diff --git a/.eslintignore b/.eslintignore index 579bd55947..1dac74b117 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,6 +3,8 @@ public dist packages/server/builder packages/server/coverage +packages/worker/coverage +packages/backend-core/coverage packages/server/client packages/builder/.routify packages/builder/cypress/support/queryLevelTransformerFunction.js diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 457d2c1451..6ace2303d9 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -6,6 +6,8 @@ labels: bug assignees: '' --- +**Checklist** +- [ ] I have searched budibase discussions and github issues to check if my issue already exists **Hosting** diff --git a/.github/workflows/budibase_ci.yml b/.github/workflows/budibase_ci.yml index 475bd4f66a..c07f9b2c28 100644 --- a/.github/workflows/budibase_ci.yml +++ b/.github/workflows/budibase_ci.yml @@ -58,7 +58,7 @@ jobs: - uses: codecov/codecov-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos - files: ./packages/server/coverage/clover.xml + files: ./packages/server/coverage/clover.xml,./packages/worker/coverage/clover.xml,./packages/backend-core/coverage/clover.xml name: codecov-umbrella verbose: true diff --git a/.github/workflows/deploy-cloud.yaml b/.github/workflows/deploy-cloud.yaml index 869a88a5b3..644eb5f1be 100644 --- a/.github/workflows/deploy-cloud.yaml +++ b/.github/workflows/deploy-cloud.yaml @@ -38,17 +38,6 @@ jobs: fi 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:prod - docker tag proxy-service budibase/proxy:$PROD_TAG - docker push budibase/proxy:$PROD_TAG - env: - DOCKER_USER: ${{ secrets.DOCKER_USERNAME }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }} - PROD_TAG: k8s - - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v1 with: diff --git a/.github/workflows/deploy-preprod.yml b/.github/workflows/deploy-preprod.yml index c3f690f568..cef47636ee 100644 --- a/.github/workflows/deploy-preprod.yml +++ b/.github/workflows/deploy-preprod.yml @@ -28,17 +28,6 @@ jobs: 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: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 }}" \ diff --git a/.github/workflows/deploy-release.yml b/.github/workflows/deploy-release.yml index b37ff9cee8..cff26fd7c8 100644 --- a/.github/workflows/deploy-release.yml +++ b/.github/workflows/deploy-release.yml @@ -29,17 +29,6 @@ jobs: 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 }}" \ diff --git a/.github/workflows/release-develop.yml b/.github/workflows/release-develop.yml index 21c74851e1..1ac6b20003 100644 --- a/.github/workflows/release-develop.yml +++ b/.github/workflows/release-develop.yml @@ -26,7 +26,7 @@ env: FEATURE_PREVIEW_URL: https://budirelease.live jobs: - release: + release-images: runs-on: ubuntu-latest steps: @@ -44,19 +44,12 @@ jobs: run: yarn install:pro develop - run: yarn - - run: yarn bootstrap + - run: yarn bootstrap - run: yarn lint - run: yarn build - run: yarn build:sdk - run: yarn test - - 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: Publish budibase packages to NPM env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} @@ -76,22 +69,25 @@ jobs: DOCKER_USER: ${{ secrets.DOCKER_USERNAME }} DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }} - - name: Get the latest budibase release version + deploy-to-release-env: + needs: [release-images] + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Get the current budibase release version id: version - run: | + 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: 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: Pull values.yaml from budibase-infra run: | @@ -149,3 +145,54 @@ jobs: 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 }} + + release-helm-chart: + needs: [release-images] + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Setup Helm + uses: azure/setup-helm@v1 + id: helm-install + + # due to helm repo index issue: https://github.com/helm/helm/issues/7363 + # we need to create new package in a different dir, merge the index and move the package back + - name: Build and release helm chart + run: | + git config user.name "Budibase Helm Bot" + git config user.email "<>" + git reset --hard + git pull + mkdir sync + echo "Packaging chart to sync dir" + helm package charts/budibase --version 0.0.0-develop --app-version develop --destination sync + echo "Packaging successful" + git checkout gh-pages + echo "Indexing helm repo" + helm repo index --merge docs/index.yaml sync + mv -f sync/* docs + rm -rf sync + echo "Pushing new helm release" + git add -A + git commit -m "Helm Release: develop" + git push + + trigger-deploy-to-qa-env: + needs: [release-helm-chart] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Get the current budibase release version + id: version + run: | + release_version=$(cat lerna.json | jq -r '.version') + echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV + + - uses: passeidireto/trigger-external-workflow-action@main + env: + PAYLOAD_VERSION: ${{ env.RELEASE_VERSION }} + with: + repository: budibase/budibase-deploys + event: deploy-budibase-develop-to-qa + github_pat: ${{ secrets.GH_ACCESS_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/release-selfhost.yml b/.github/workflows/release-selfhost.yml index d78180fdc7..12fb8f5a9d 100644 --- a/.github/workflows/release-selfhost.yml +++ b/.github/workflows/release-selfhost.yml @@ -67,16 +67,24 @@ jobs: uses: azure/setup-helm@v1 id: helm-install + # due to helm repo index issue: https://github.com/helm/helm/issues/7363 + # we need to create new package in a different dir, merge the index and move the package back - name: Build and release helm chart run: | git config user.name "Budibase Helm Bot" git config user.email "<>" git reset --hard git pull - helm package charts/budibase + mkdir sync + echo "Packaging chart to sync dir" + helm package charts/budibase --version "$RELEASE_VERSION" --app-version "$RELEASE_VERSION" --destination sync + echo "Packaging successful" git checkout gh-pages - mv *.tgz docs - helm repo index docs + echo "Indexing helm repo" + helm repo index --merge docs/index.yaml sync + mv -f sync/* docs + rm -rf sync + echo "Pushing new helm release" git add -A git commit -m "Helm Release: ${{ env.RELEASE_VERSION }}" git push diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index de288dd7db..2a28150891 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -98,17 +98,6 @@ jobs: 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 }}" \ diff --git a/.github/workflows/smoke_test.yaml b/.github/workflows/smoke_test.yaml index cffb914aaf..29c7f5f85a 100644 --- a/.github/workflows/smoke_test.yaml +++ b/.github/workflows/smoke_test.yaml @@ -18,30 +18,18 @@ jobs: - run: yarn - run: yarn bootstrap - run: yarn build - - name: Pull cypress.env.yaml from budibase-infra + - name: Pull from budibase-infra run: | curl -H "Authorization: token ${{ secrets.GH_PERSONAL_TOKEN }}" \ -H 'Accept: application/vnd.github.v3.raw' \ - -o packages/builder/cypress.env.json \ - -L https://api.github.com/repos/budibase/budibase-infra/contents/test/cypress.env.json - wc -l packages/builder/cypress.env.json - - - name: Cypress run - id: cypress - continue-on-error: true - uses: cypress-io/github-action@v2 - with: - record: true - install: false - tag: nightly - command: yarn test:e2e:ci:record - env: - CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} + -o + -L + wc -l - uses: actions/upload-artifact@v3 with: name: Test Reports - path: packages/builder/cypress/reports/testReport.html + path: # TODO: enable once running in QA test env # - name: Configure AWS Credentials @@ -54,11 +42,3 @@ jobs: # - name: Upload test results HTML # uses: aws-actions/configure-aws-credentials@v1 # run: aws s3 cp packages/builder/cypress/reports/testReport.html s3://{{ secrets.BUDI_QA_REPORTS_BUCKET_NAME }}/$GITHUB_RUN_ID/index.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 e1d3e6db0e..b3dc8af0d4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ builder/* packages/server/runtime_apps/ .idea/ bb-airgapped.tar.gz +*.iml # Logs logs @@ -65,8 +66,6 @@ typings/ .env !qa-core/.env !hosting/.env -hosting/.generated-nginx.dev.conf -hosting/proxy/.generated-nginx.prod.conf # parcel-bundler cache (https://parceljs.org/) .cache @@ -104,5 +103,9 @@ stats.html # TypeScript cache *.tsbuildinfo + +# plugins budibase-component budibase-datasource + +*.iml diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000000..e0bcfe01fb --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v14.19.3 diff --git a/.prettierignore b/.prettierignore index 3a381d255e..7eb567d517 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,6 +4,8 @@ dist packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte packages/server/builder packages/server/coverage +packages/worker/coverage +packages/backend-core/coverage packages/server/client packages/server/src/definitions/openapi.ts packages/builder/.routify diff --git a/.python-version b/.python-version new file mode 100644 index 0000000000..371cfe355d --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.11.1 diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000000..8a1af3c071 --- /dev/null +++ b/.tool-versions @@ -0,0 +1,2 @@ +nodejs 14.19.3 +python 3.11.1 \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000000..03d0aa4411 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "esbenp.prettier-vscode", + "svelte.svelte-vscode" + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 4838a4fd89..ece537efac 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,19 +1,28 @@ { - "editor.formatOnSave": true, - "editor.codeActionsOnSave": { - "source.fixAll": true - }, - "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/**", - "/**" - ] - }, + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll": true + }, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "debug.javascript.terminalOptions": { + "skipFiles": [ + "${workspaceFolder}/packages/backend-core/node_modules/**", + "/**" + ] + }, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[dockercompose]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[svelte]": { + "editor.defaultFormatter": "svelte.svelte-vscode" + } } diff --git a/artifacthub-repo.yml b/artifacthub-repo.yml new file mode 100644 index 0000000000..304b2bc79b --- /dev/null +++ b/artifacthub-repo.yml @@ -0,0 +1,10 @@ +# Artifact Hub repository metadata file +# This file is used to verify ownership of the budibase Helm chart repo +# so that we appear as a verified owner on artifacthub.io + +repositoryID: a7536764-e72e-4961-87d8-efe7c8dedfa3 +owners: # (optional, used to claim repository ownership) + - name: Martin + email: budimaster@budibase.com + - name: DevOps + email: devops@budibase.com \ No newline at end of file diff --git a/charts/budibase/Chart.yaml b/charts/budibase/Chart.yaml index 570aa04d8e..05b3f24dbd 100644 --- a/charts/budibase/Chart.yaml +++ b/charts/budibase/Chart.yaml @@ -11,11 +11,13 @@ sources: - https://github.com/Budibase/budibase - https://budibase.com type: application -version: 0.2.11 -appVersion: 1.0.214 +# populates on packaging +version: 0.0.0 +# populates on packaging +appVersion: 0.0.0 dependencies: - name: couchdb - version: 3.6.1 + version: 3.3.4 repository: https://apache.github.io/couchdb-helm condition: services.couchdb.enabled - name: ingress-nginx diff --git a/charts/budibase/templates/alb-ingress.yaml b/charts/budibase/templates/alb-ingress.yaml index 388bcf1d3e..c128b70843 100644 --- a/charts/budibase/templates/alb-ingress.yaml +++ b/charts/budibase/templates/alb-ingress.yaml @@ -14,6 +14,9 @@ metadata: alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]' alb.ingress.kubernetes.io/certificate-arn: {{ .Values.ingress.certificateArn }} {{- end }} + {{- if .Values.ingress.securityGroups }} + alb.ingress.kubernetes.io/security-groups: {{ .Values.ingress.securityGroups }} + {{- end }} spec: rules: - http: diff --git a/charts/budibase/templates/app-service-deployment.yaml b/charts/budibase/templates/app-service-deployment.yaml index f72d1aef03..9ac8a1e7c6 100644 --- a/charts/budibase/templates/app-service-deployment.yaml +++ b/charts/budibase/templates/app-service-deployment.yaml @@ -20,6 +20,9 @@ spec: annotations: kompose.cmd: kompose convert kompose.version: 1.21.0 (992df58d8) +{{ if .Values.services.apps.annotations }} +{{- toYaml .Values.services.apps.annotations | indent 8 -}} +{{ end }} creationTimestamp: null labels: io.kompose.service: app-service @@ -60,12 +63,12 @@ spec: secretKeyRef: name: {{ template "budibase.fullname" . }} key: jwtSecret - - name: LOG_LEVEL - value: {{ .Values.services.apps.logLevel | default "info" | quote }} {{ if .Values.services.objectStore.region }} - name: AWS_REGION value: {{ .Values.services.objectStore.region }} {{ end }} + - name: MINIO_ENABLED + value: {{ .Values.services.objectStore.minio | quote }} - name: MINIO_ACCESS_KEY valueFrom: secretKeyRef: @@ -76,10 +79,22 @@ spec: secretKeyRef: name: {{ template "budibase.fullname" . }} key: objectStoreSecret + - name: CLOUDFRONT_CDN + value: {{ .Values.services.objectStore.cloudfront.cdn | quote }} + - name: CLOUDFRONT_PUBLIC_KEY_ID + value: {{ .Values.services.objectStore.cloudfront.publicKeyId | quote }} + - name: CLOUDFRONT_PRIVATE_KEY_64 + value: {{ .Values.services.objectStore.cloudfront.privateKey64 | quote }} - name: MINIO_URL value: {{ .Values.services.objectStore.url }} - name: PLUGIN_BUCKET_NAME - value: {{ .Values.services.objectStore.pluginBucketName | default "plugins" | quote }} + value: {{ .Values.services.objectStore.pluginBucketName | quote }} + - name: APPS_BUCKET_NAME + value: {{ .Values.services.objectStore.appsBucketName | quote }} + - name: GLOBAL_BUCKET_NAME + value: {{ .Values.services.objectStore.globalBucketName | quote }} + - name: BACKUPS_BUCKET_NAME + value: {{ .Values.services.objectStore.backupsBucketName | quote }} - name: PORT value: {{ .Values.services.apps.port | quote }} {{ if .Values.services.worker.publicApiRateLimitPerSecond }} @@ -124,6 +139,8 @@ spec: value: {{ .Values.globals.automationMaxIterations | quote }} - name: TENANT_FEATURE_FLAGS value: {{ .Values.globals.tenantFeatureFlags | quote }} + - name: ENCRYPTION_KEY + value: {{ .Values.globals.bbEncryptionKey | quote }} {{ if .Values.globals.bbAdminUserEmail }} - name: BB_ADMIN_USER_EMAIL value: {{ .Values.globals.bbAdminUserEmail | quote }} @@ -152,6 +169,24 @@ spec: - name: ELASTIC_APM_SERVER_URL value: {{ .Values.globals.elasticApmServerUrl | quote }} {{ end }} + {{ if .Values.globals.globalAgentHttpProxy }} + - name: GLOBAL_AGENT_HTTP_PROXY + value: {{ .Values.globals.globalAgentHttpProxy | quote }} + {{ end }} + {{ if .Values.globals.globalAgentHttpsProxy }} + - name: GLOBAL_AGENT_HTTPS_PROXY + value: {{ .Values.globals.globalAgentHttpsProxy | quote }} + {{ end }} + {{ if .Values.globals.globalAgentNoProxy }} + - name: GLOBAL_AGENT_NO_PROXY + value: {{ .Values.globals.globalAgentNoProxy | quote }} + {{ end }} + - name: CDN_URL + value: {{ .Values.globals.cdnUrl }} + {{ if .Values.services.tlsRejectUnauthorized }} + - name: NODE_TLS_REJECT_UNAUTHORIZED + value: {{ .Values.services.tlsRejectUnauthorized }} + {{ end }} image: budibase/apps:{{ .Values.globals.appVersion }} imagePullPolicy: Always diff --git a/charts/budibase/templates/minio-service-deployment.yaml b/charts/budibase/templates/minio-service-deployment.yaml index 144dbe539a..d0a367653d 100644 --- a/charts/budibase/templates/minio-service-deployment.yaml +++ b/charts/budibase/templates/minio-service-deployment.yaml @@ -42,6 +42,7 @@ spec: secretKeyRef: name: {{ template "budibase.fullname" . }} key: objectStoreSecret + image: minio/minio imagePullPolicy: "" livenessProbe: diff --git a/charts/budibase/templates/proxy-service-deployment.yaml b/charts/budibase/templates/proxy-service-deployment.yaml index 5588022032..6064905c4c 100644 --- a/charts/budibase/templates/proxy-service-deployment.yaml +++ b/charts/budibase/templates/proxy-service-deployment.yaml @@ -20,16 +20,34 @@ spec: annotations: kompose.cmd: kompose convert kompose.version: 1.21.0 (992df58d8) +{{ if .Values.services.proxy.annotations }} +{{- toYaml .Values.services.proxy.annotations | indent 8 -}} +{{ end }} creationTimestamp: null labels: app.kubernetes.io/name: budibase-proxy spec: containers: - - image: budibase/proxy:{{ .Values.services.proxy.tag | default "k8s" }} + - image: budibase/proxy:{{ .Values.globals.appVersion }} imagePullPolicy: Always name: proxy-service ports: - containerPort: {{ .Values.services.proxy.port }} + env: + - name: APPS_UPSTREAM_URL + value: {{ tpl .Values.services.proxy.upstreams.apps . | quote }} + - name: WORKER_UPSTREAM_URL + value: {{ tpl .Values.services.proxy.upstreams.worker . | quote }} + - name: MINIO_UPSTREAM_URL + value: {{ tpl .Values.services.proxy.upstreams.minio . | quote }} + - name: COUCHDB_UPSTREAM_URL + value: {{ .Values.services.couchdb.url | default (tpl .Values.services.proxy.upstreams.couchdb .) | quote }} + - name: RESOLVER + {{ if .Values.services.proxy.resolver }} + value: {{ .Values.services.proxy.resolver }} + {{ else }} + value: kube-dns.kube-system.svc.{{ .Values.services.dns }} + {{ end }} {{ with .Values.services.proxy.resources }} resources: {{- toYaml . | nindent 10 }} diff --git a/charts/budibase/templates/redis-service-deployment.yaml b/charts/budibase/templates/redis-service-deployment.yaml index d94e4d70f8..5916c6d3f9 100644 --- a/charts/budibase/templates/redis-service-deployment.yaml +++ b/charts/budibase/templates/redis-service-deployment.yaml @@ -60,5 +60,6 @@ spec: - name: redis-data persistentVolumeClaim: claimName: redis-data + status: {} {{- end }} diff --git a/charts/budibase/templates/worker-service-deployment.yaml b/charts/budibase/templates/worker-service-deployment.yaml index b1c6110d95..a16f839ea7 100644 --- a/charts/budibase/templates/worker-service-deployment.yaml +++ b/charts/budibase/templates/worker-service-deployment.yaml @@ -21,6 +21,9 @@ spec: annotations: kompose.cmd: kompose convert kompose.version: 1.21.0 (992df58d8) +{{ if .Values.services.worker.annotations }} +{{- toYaml .Values.services.worker.annotations | indent 8 -}} +{{ end }} creationTimestamp: null labels: io.kompose.service: worker-service @@ -65,6 +68,8 @@ spec: - name: AWS_REGION value: {{ .Values.services.objectStore.region }} {{ end }} + - name: MINIO_ENABLED + value: {{ .Values.services.objectStore.minio | quote }} - name: MINIO_ACCESS_KEY valueFrom: secretKeyRef: @@ -77,8 +82,20 @@ spec: key: objectStoreSecret - name: MINIO_URL value: {{ .Values.services.objectStore.url }} + - name: CLOUDFRONT_CDN + value: {{ .Values.services.objectStore.cloudfront.cdn | quote }} + - name: CLOUDFRONT_PUBLIC_KEY_ID + value: {{ .Values.services.objectStore.cloudfront.publicKeyId | quote }} + - name: CLOUDFRONT_PRIVATE_KEY_64 + value: {{ .Values.services.objectStore.cloudfront.privateKey64 | quote }} - name: PLUGIN_BUCKET_NAME - value: {{ .Values.services.objectStore.pluginBucketName | default "plugins" | quote }} + value: {{ .Values.services.objectStore.pluginBucketName | quote }} + - name: APPS_BUCKET_NAME + value: {{ .Values.services.objectStore.appsBucketName | quote }} + - name: GLOBAL_BUCKET_NAME + value: {{ .Values.services.objectStore.globalBucketName | quote }} + - name: BACKUPS_BUCKET_NAME + value: {{ .Values.services.objectStore.backupsBucketName | quote }} - name: PORT value: {{ .Values.services.worker.port | quote }} - name: MULTI_TENANCY @@ -129,6 +146,8 @@ spec: value: {{ .Values.globals.google.secret | quote }} - name: TENANT_FEATURE_FLAGS value: {{ .Values.globals.tenantFeatureFlags | quote }} + - name: ENCRYPTION_KEY + value: {{ .Values.globals.bbEncryptionKey | quote }} {{ if .Values.globals.elasticApmEnabled }} - name: ELASTIC_APM_ENABLED value: {{ .Values.globals.elasticApmEnabled | quote }} @@ -141,6 +160,24 @@ spec: - name: ELASTIC_APM_SERVER_URL value: {{ .Values.globals.elasticApmServerUrl | quote }} {{ end }} + {{ if .Values.globals.globalAgentHttpProxy }} + - name: GLOBAL_AGENT_HTTP_PROXY + value: {{ .Values.globals.globalAgentHttpProxy | quote }} + {{ end }} + {{ if .Values.globals.globalAgentHttpsProxy }} + - name: GLOBAL_AGENT_HTTPS_PROXY + value: {{ .Values.globals.globalAgentHttpsProxy | quote }} + {{ end }} + {{ if .Values.globals.globalAgentNoProxy }} + - name: GLOBAL_AGENT_NO_PROXY + value: {{ .Values.globals.globalAgentNoProxy | quote }} + {{ end }} + - name: CDN_URL + value: {{ .Values.globals.cdnUrl }} + {{ if .Values.services.tlsRejectUnauthorized }} + - name: NODE_TLS_REJECT_UNAUTHORIZED + value: {{ .Values.services.tlsRejectUnauthorized }} + {{ end }} image: budibase/worker:{{ .Values.globals.appVersion }} imagePullPolicy: Always diff --git a/charts/budibase/values.yaml b/charts/budibase/values.yaml index 5c4004cb57..dd75b2daa3 100644 --- a/charts/budibase/values.yaml +++ b/charts/budibase/values.yaml @@ -76,7 +76,7 @@ affinity: {} globals: appVersion: "latest" budibaseEnv: PRODUCTION - tenantFeatureFlags: "*:LICENSING,*:USER_GROUPS" + tenantFeatureFlags: "*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR" enableAnalytics: "1" sentryDSN: "" posthogToken: "phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU" @@ -98,6 +98,7 @@ globals: # if createSecrets is set to false, you can hard-code your secrets here internalApiKey: "" jwtSecret: "" + cdnUrl: "" smtp: enabled: false @@ -105,15 +106,28 @@ globals: # elasticApmEnabled: # elasticApmSecretToken: # elasticApmServerUrl: +# globalAgentHttpProxy: +# globalAgentHttpsProxy: +# globalAgentNoProxy: services: budibaseVersion: latest dns: cluster.local + # tlsRejectUnauthorized: 0 proxy: port: 10000 replicaCount: 1 + upstreams: + apps: 'http://app-service.{{ .Release.Namespace }}.svc.{{ .Values.services.dns }}:{{ .Values.services.apps.port }}' + worker: 'http://worker-service.{{ .Release.Namespace }}.svc.{{ .Values.services.dns }}:{{ .Values.services.worker.port }}' + minio: 'http://minio-service.{{ .Release.Namespace }}.svc.{{ .Values.services.dns }}:{{ .Values.services.objectStore.port }}' + couchdb: 'http://{{ .Release.Name }}-svc-couchdb:{{ .Values.services.couchdb.port }}' resources: {} +# annotations: +# co.elastic.logs/module: nginx +# co.elastic.logs/fileset.stdout: access +# co.elastic.logs/fileset.stderr: error apps: port: 4002 @@ -121,11 +135,20 @@ services: logLevel: info resources: {} # nodeDebug: "" # set the value of NODE_DEBUG - +# annotations: +# co.elastic.logs/multiline.type: pattern +# co.elastic.logs/multiline.pattern: '^[[:space:]]' +# co.elastic.logs/multiline.negate: false +# co.elastic.logs/multiline.match: after worker: port: 4003 replicaCount: 1 resources: {} +# annotations: +# co.elastic.logs/multiline.type: pattern +# co.elastic.logs/multiline.pattern: '^[[:space:]]' +# co.elastic.logs/multiline.negate: false +# co.elastic.logs/multiline.match: after couchdb: enabled: true @@ -156,6 +179,7 @@ services: resources: {} objectStore: + # Set to false if using another object store such as S3 minio: true browser: true port: 9000 @@ -171,6 +195,13 @@ services: ## set, choosing the default provisioner. storageClass: "" resources: {} + cloudfront: + # Set the url of a distribution to enable cloudfront + cdn: "" + # ID of public key stored in cloudfront + publicKeyId: "" + # Base64 encoded private key for the above public key + privateKey64: "" # Override values in couchDB subchart couchdb: diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index fb0848596c..6e667d23a8 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -9,7 +9,6 @@ From opening a bug report to creating a pull request: every contribution is appr - [Glossary of Terms](#glossary-of-terms) - [Contributing to Budibase](#contributing-to-budibase) - ## Not Sure Where to Start? Budibase is a low-code web application builder that creates svelte-based web applications. @@ -22,7 +21,7 @@ Budibase is a monorepo managed by [lerna](https://github.com/lerna/lerna). Lerna - **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. +- **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) @@ -45,7 +44,7 @@ A client represents a single budibase customer. Each budibase client will have 1 ### 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. +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 @@ -73,28 +72,55 @@ 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`. +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 maintain the existing code style. -* Please try to keep your commits small and focused. +- Please try to keep your commits small and focused. -* Please write tests. +- Please write tests. -* If the project diverges from your branch, please rebase instead of merging. This makes the commit graph easier to read. +- 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. +- 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` +#### 1. Prerequisites -*yarn -* `npm install -g yarn` +- NodeJS version `14.x.x` +- Python version `3.x` -*jest* - `npm install -g jest` +### Using asdf (recommended) + +Asdf is a package manager that allows managing multiple dependencies. + +You can install them following any of the steps described below: + +- Install using script (only for mac users): + +`./scripts/install-contributor-dependencies.sh` + +- Or, manually: + + - Installation steps: https://asdf-vm.com/guide/getting-started.html + - asdf plugin add nodejs + - asdf plugin add python + - npm install -g yarn + +### Using NVM and pyenv + +- NVM: + - Install: https://github.com/nvm-sh/nvm#installing-and-updating + - Setup: `nvm use` +- Pyenv: + + - Install: https://github.com/pyenv/pyenv#installation + - Setup: `pyenv install -v 3.7.2` + +- _yarn -_ `npm install -g yarn` #### 2. Clone this repository @@ -102,7 +128,7 @@ NodeJS Version `14.x.x` then `cd ` into your local copy. -#### 3. Install and Build +#### 3. Install and Build | **NOTE**: On Windows, all yarn commands must be executed on a bash shell (e.g. git bash) @@ -134,9 +160,9 @@ This will enable watch mode for both the builder app, server, client library and #### 5. Debugging using VS Code -To debug the budibase server and worker a VS Code launch configuration has been provided. +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. +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`. @@ -156,11 +182,11 @@ For the backend we run [Redis](https://redis.io/), [CouchDB](https://couchdb.apa 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` +- `redis_data` - Sessions, email tokens -- `couchdb3_data` +- `couchdb3_data` - Global and app databases -- `minio_data` +- `minio_data` - App manifest, budibase client, static assets ### Development Modes @@ -172,34 +198,42 @@ 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. + +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. + +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 +#### 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](../.github/workflows/README.md) + +An overview of the CI pipelines can be found [here](../.github/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/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. ``` . @@ -207,13 +241,14 @@ yarn mode:account |_ budibase-pro ``` -Note that only budibase maintainers will be able to access the pro repo. +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. +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 @@ -226,12 +261,11 @@ 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). +- 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). +- 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. +- 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 index 88a124708c..9edd8286cb 100644 --- a/docs/DEV-SETUP-DEBIAN.md +++ b/docs/DEV-SETUP-DEBIAN.md @@ -1,12 +1,15 @@ ## Dev Environment on Debian 11 -### Install Node +### Install NVM & Node 14 +NVM documentation: https://github.com/nvm-sh/nvm#installing-and-updating -Budibase requires a recent version of node (14+): +Install NVM ``` -curl -sL https://deb.nodesource.com/setup_16.x | sudo bash - -apt -y install nodejs -node -v +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash +``` +Install Node 14 +``` +nvm install 14 ``` ### Install npm requirements @@ -31,7 +34,7 @@ This setup process was tested on Debian 11 (bullseye) with version numbers show - Docker: 20.10.5 - Docker-Compose: 1.29.2 -- Node: v16.15.1 +- Node: v14.20.1 - Yarn: 1.22.19 - Lerna: 5.1.4 diff --git a/docs/DEV-SETUP-MACOSX.md b/docs/DEV-SETUP-MACOSX.md index c5990e58da..d9e2dcad6a 100644 --- a/docs/DEV-SETUP-MACOSX.md +++ b/docs/DEV-SETUP-MACOSX.md @@ -11,7 +11,7 @@ through brew. ### Install Node -Budibase requires a recent version of node (14+): +Budibase requires a recent version of node 14: ``` brew install node npm node -v @@ -38,7 +38,7 @@ This setup process was tested on Mac OSX 12 (Monterey) with version numbers show - Docker: 20.10.14 - Docker-Compose: 2.6.0 -- Node: 18.3.0 +- Node: 14.20.1 - Yarn: 1.22.19 - Lerna: 5.1.4 @@ -59,4 +59,7 @@ The dev version will be available on port 10000 i.e. http://127.0.0.1:10000/builder/admin | **NOTE**: If you are working on a M1 Apple Silicon, you will need to uncomment `# platform: linux/amd64` line in -[hosting/docker-compose-dev.yaml](../hosting/docker-compose.dev.yaml) \ No newline at end of file +[hosting/docker-compose-dev.yaml](../hosting/docker-compose.dev.yaml) + +### Troubleshooting +If there are errors with the `yarn setup` command, you can try installing nvm and node 14. This is the same as the instructions for Debian 11. diff --git a/docs/DEV-SETUP-WINDOWS.md b/docs/DEV-SETUP-WINDOWS.md new file mode 100644 index 0000000000..c5608b7567 --- /dev/null +++ b/docs/DEV-SETUP-WINDOWS.md @@ -0,0 +1,81 @@ +## Dev Environment on Windows 10/11 (WSL2) + + +### Install WSL with Ubuntu LTS + +Enable WSL 2 on Windows 10/11 for docker support. +``` +wsl --set-default-version 2 +``` +Install Ubuntu LTS. +``` +wsl --install Ubuntu +``` + +Or follow the instruction here: +https://learn.microsoft.com/en-us/windows/wsl/install + +### Install Docker in windows +Download the installer from docker and install it. + +Check this url for more detailed instructions: +https://docs.docker.com/desktop/install/windows-install/ + +You should follow the next steps from within the Ubuntu terminal. + +### Install NVM & Node 14 +NVM documentation: https://github.com/nvm-sh/nvm#installing-and-updating + +Install NVM +``` +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash +``` +Install Node 14 +``` +nvm install 14 +``` + + +### Install npm requirements + +``` +npm install -g yarn jest lerna +``` + +### Clone the repo +``` +git clone https://github.com/Budibase/budibase.git +``` + +### Check Versions + +This setup process was tested on Windows 11 with version numbers show below. Your mileage may vary using anything else. + +- Docker: 20.10.7 +- Docker-Compose: 2.10.2 +- Node: v14.20.1 +- Yarn: 1.22.19 +- Lerna: 5.5.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 + +### Working with the code +Here are the instructions to work on the application from within Visual Studio Code (in Windows) through the WSL. All the commands and files are within the Ubuntu system and it should run as if you were working on a Linux machine. + +https://code.visualstudio.com/docs/remote/wsl + +Note you will be able to run the application from within the WSL terminal and you will be able to access the application from the a browser in Windows. \ No newline at end of file diff --git a/examples/nextjs-api-sales/yarn.lock b/examples/nextjs-api-sales/yarn.lock index f47fb84e33..1f34ca2ee2 100644 --- a/examples/nextjs-api-sales/yarn.lock +++ b/examples/nextjs-api-sales/yarn.lock @@ -2,27 +2,6 @@ # yarn lockfile v1 -"@babel/code-frame@^7.0.0": - 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/highlight" "^7.16.7" - -"@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/highlight@^7.16.7": - version "7.16.10" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.10.tgz#744f2eb81579d6eea753c227b0f570ad785aba88" - integrity sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw== - dependencies: - "@babel/helper-validator-identifier" "^7.16.7" - chalk "^2.0.0" - js-tokens "^4.0.0" - "@babel/runtime-corejs3@^7.10.2": version "7.17.2" resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.17.2.tgz#fdca2cd05fba63388babe85d349b6801b008fd13" @@ -53,11 +32,6 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" -"@gar/promisify@^1.0.1": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" - integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== - "@humanwhocodes/config-array@^0.9.2": version "0.9.5" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.5.tgz#2cbaf9a89460da24b5ca6531b8bbfc23e1df50c7" @@ -160,52 +134,21 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@npmcli/fs@^1.0.0": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.1.tgz#72f719fe935e687c56a4faecf3c03d06ba593257" - integrity sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ== - dependencies: - "@gar/promisify" "^1.0.1" - semver "^7.3.5" - -"@npmcli/move-file@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" - integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== - dependencies: - mkdirp "^1.0.4" - rimraf "^3.0.2" - "@rushstack/eslint-patch@^1.0.8": version "1.1.0" resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.1.0.tgz#7f698254aadf921e48dda8c0a6b304026b8a9323" integrity sha512-JLo+Y592QzIE+q7Dl2pMUtt4q8SKYI5jDrZxrozEQxnGVOyYE+GWK9eLkwTaeN9DDctlaRAQ3TBmzZ1qdLE30A== -"@tootallnate/once@1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" - integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== - "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= -"@types/minimist@^1.2.0": - version "1.2.2" - resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" - integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== - "@types/node@17.0.21": version "17.0.21" resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.21.tgz#864b987c0c68d07b4345845c3e63b75edd143644" integrity sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ== -"@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/prop-types@*": version "15.7.4" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" @@ -269,11 +212,6 @@ "@typescript-eslint/types" "5.14.0" eslint-visitor-keys "^3.0.0" -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== - acorn-jsx@^5.3.1: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -284,31 +222,7 @@ acorn@^8.7.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== -agent-base@6, agent-base@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" - integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== - dependencies: - debug "4" - -agentkeepalive@^4.1.3: - version "4.2.1" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.2.1.tgz#a7975cbb9f83b367f06c90cc51ff28fe7d499717" - integrity sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA== - dependencies: - debug "^4.1.0" - depd "^1.1.2" - humanize-ms "^1.2.1" - -aggregate-error@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" - integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== - dependencies: - clean-stack "^2.0.0" - indent-string "^4.0.0" - -ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4: +ajv@^6.10.0, ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -323,40 +237,20 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: +ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: color-convert "^2.0.1" -"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== +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== dependencies: - delegates "^1.0.0" - readable-stream "^3.6.0" - -are-we-there-yet@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-3.0.0.tgz#ba20bd6b553e31d62fc8c31bd23d22b95734390d" - integrity sha512-0GWpv50YSOcLXaN6/FAKY3vfRbllXWV2xvfA/oKJF8pzFhWXPV+yjhJXDBbjscDYowv7Yw1A3uigpzn5iEGTyw== - dependencies: - delegates "^1.0.0" - readable-stream "^3.6.0" + normalize-path "^3.0.0" + picomatch "^2.0.4" argparse@^2.0.1: version "2.0.1" @@ -405,48 +299,11 @@ array.prototype.flatmap@^1.2.5: define-properties "^1.1.3" es-abstract "^1.19.0" -arrify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" - integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= - -asn1@~0.2.3: - 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= - ast-types-flow@^0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0= -async-foreach@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542" - integrity sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI= - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= - -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.8.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" - integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== - axe-core@^4.3.5: version "4.4.1" resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.1.tgz#7dbdc25989298f9ad006645cd396782443757413" @@ -462,12 +319,10 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= - dependencies: - tweetnacl "^0.14.3" +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== brace-expansion@^1.1.7: version "1.1.11" @@ -477,7 +332,7 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^3.0.1: +braces@^3.0.1, 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== @@ -489,30 +344,6 @@ bulma@^0.9.3: resolved "https://registry.yarnpkg.com/bulma/-/bulma-0.9.3.tgz#ddccb7436ebe3e21bf47afe01d3c43a296b70243" integrity sha512-0d7GNW1PY4ud8TWxdNcP6Cc8Bu7MxcntD/RRLGWuiw/s0a9P+XlH/6QoOIrmbj6o8WWJzJYhytiu9nFjTszk1g== -cacache@^15.2.0: - version "15.3.0" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" - integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== - dependencies: - "@npmcli/fs" "^1.0.0" - "@npmcli/move-file" "^1.0.1" - chownr "^2.0.0" - fs-minipass "^2.0.0" - glob "^7.1.4" - infer-owner "^1.0.4" - lru-cache "^6.0.0" - minipass "^3.1.1" - minipass-collect "^1.0.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.2" - mkdirp "^1.0.3" - p-map "^4.0.0" - promise-inflight "^1.0.1" - rimraf "^3.0.2" - ssri "^8.0.1" - tar "^6.0.2" - unique-filename "^1.1.1" - call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -526,40 +357,12 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -camelcase-keys@^6.2.2: - version "6.2.2" - resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" - integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg== - dependencies: - camelcase "^5.3.1" - map-obj "^4.0.0" - quick-lru "^4.0.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== - caniuse-lite@^1.0.30001283: version "1.0.30001314" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001314.tgz#65c7f9fb7e4594fca0a333bec1d8939662377596" integrity sha512-0zaSO+TnCHtHJIbpLroX7nsD+vYuOVjl3uzFbJO1wMVbuveJA0RK2WcQA9ZUIOiO0/ArMiMgHJLxfEZhQiC0kw== -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= - -chalk@^2.0.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^4.0.0, chalk@^4.1.2: +chalk@^4.0.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -567,31 +370,20 @@ chalk@^4.0.0, chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" -chownr@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" - integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== - -clean-stack@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" - integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== - -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== +"chokidar@>=3.0.0 <4.0.0": + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.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" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" + 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" color-convert@^2.0.1: version "2.0.1" @@ -600,54 +392,22 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -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= - 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== -color-support@^1.1.2, color-support@^1.1.3: - 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.6: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - 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= -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 sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= - core-js-pure@^3.20.2: version "3.21.1" resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.21.1.tgz#8c4d1e78839f5f46208de7230cebfb72bc3bdb51" integrity sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ== -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= - -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.2, cross-spawn@^7.0.3: +cross-spawn@^7.0.2: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -666,25 +426,11 @@ damerau-levenshtein@^1.0.7: resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== -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= - dependencies: - assert-plus "^1.0.0" - data-uri-to-buffer@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz#b5db46aea50f6176428ac05b73be39a57701a64b" integrity sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA== -debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: - version "4.3.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" - integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== - dependencies: - ms "2.1.2" - debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -699,18 +445,12 @@ debug@^3.2.7: dependencies: ms "^2.1.1" -decamelize-keys@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" - integrity sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk= +debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: + version "4.3.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" + integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== dependencies: - decamelize "^1.1.0" - map-obj "^1.0.0" - -decamelize@^1.1.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= + ms "2.1.2" deep-is@^0.1.3: version "0.1.4" @@ -724,21 +464,6 @@ define-properties@^1.1.3: dependencies: object-keys "^1.0.12" -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= - -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= - -depd@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= - dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -760,48 +485,11 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -ecc-jsbn@~0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" - integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= - dependencies: - jsbn "~0.1.0" - safer-buffer "^2.1.0" - -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== - emoji-regex@^9.2.2: version "9.2.2" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== -encoding@^0.1.12: - version "0.1.13" - resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" - integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== - dependencies: - iconv-lite "^0.6.2" - -env-paths@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" - integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== - -err-code@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" - integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - es-abstract@^1.19.0, es-abstract@^1.19.1: version "1.19.1" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.1.tgz#d4885796876916959de78edaa0df456627115ec3" @@ -837,16 +525,6 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -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-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= - escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" @@ -1055,21 +733,6 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -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== - -extsprintf@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= - -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, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -1132,14 +795,6 @@ find-up@^2.1.0: dependencies: locate-path "^2.0.0" -find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - flat-cache@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" @@ -1153,20 +808,6 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3" integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg== -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= - -form-data@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" - integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" - formdata-polyfill@^4.0.10: version "4.0.10" resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" @@ -1174,18 +815,16 @@ formdata-polyfill@^4.0.10: dependencies: fetch-blob "^3.1.2" -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= +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== + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -1196,48 +835,6 @@ functional-red-black-tree@^1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= -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" - -gauge@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.2.tgz#c3777652f542b6ef62797246e8c7caddecb32cc7" - integrity sha512-aSPRm2CvA9R8QyU5eXMFPd+cYkyxLsXHd2l5/FOH2V/eml//M04G6KZOmTap07O1PvEwNcl2NndyLfK8g3QrKA== - dependencies: - ansi-regex "^5.0.1" - aproba "^1.0.3 || ^2.0.0" - color-support "^1.1.3" - console-control-strings "^1.1.0" - has-unicode "^2.0.1" - signal-exit "^3.0.7" - string-width "^4.2.3" - strip-ansi "^6.0.1" - wide-align "^1.1.5" - -gaze@^1.0.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a" - integrity sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g== - dependencies: - globule "^1.0.0" - -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== - get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" @@ -1247,11 +844,6 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: has "^1.0.3" has-symbols "^1.0.1" -get-stdin@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" - integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= - get-symbol-description@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" @@ -1260,14 +852,7 @@ get-symbol-description@^1.0.0: call-bind "^1.0.2" get-intrinsic "^1.1.1" -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= - dependencies: - assert-plus "^1.0.0" - -glob-parent@^5.1.2: +glob-parent@^5.1.2, 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== @@ -1281,7 +866,7 @@ glob-parent@^6.0.1: dependencies: is-glob "^4.0.3" -glob@7.1.7, glob@~7.1.1: +glob@7.1.7: version "7.1.7" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== @@ -1293,7 +878,7 @@ glob@7.1.7, glob@~7.1.1: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.0, glob@^7.0.3, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.7: +glob@^7.1.3, glob@^7.1.7: version "7.2.0" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== @@ -1324,48 +909,11 @@ globby@^11.0.4: merge2 "^1.4.1" slash "^3.0.0" -globule@^1.0.0: - version "1.3.3" - resolved "https://registry.yarnpkg.com/globule/-/globule-1.3.3.tgz#811919eeac1ab7344e905f2e3be80a13447973c2" - integrity sha512-mb1aYtDbIjTu4ShMB85m3UzjX9BVKe9WCzsnfMSZk+K5GpIbBOexgg4PPCt5eHDEG5/ZQAUX2Kct02zfiPLsKg== - dependencies: - glob "~7.1.1" - lodash "~4.17.10" - minimatch "~3.0.2" - -graceful-fs@^4.2.6: - version "4.2.9" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" - integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== - -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@~5.1.3: - version "5.1.5" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" - integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== - dependencies: - ajv "^6.12.3" - har-schema "^2.0.0" - -hard-rejection@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" - integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== - has-bigints@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== -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= - has-flag@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" @@ -1383,11 +931,6 @@ has-tostringtag@^1.0.0: dependencies: has-symbols "^1.0.2" -has-unicode@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= - has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -1395,63 +938,6 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" -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== - -hosted-git-info@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz#827b82867e9ff1c8d0c4d9d53880397d2c86d224" - integrity sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA== - dependencies: - lru-cache "^6.0.0" - -http-cache-semantics@^4.1.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-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" - integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== - dependencies: - "@tootallnate/once" "1" - agent-base "6" - debug "4" - -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= - 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== - dependencies: - agent-base "6" - debug "4" - -humanize-ms@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" - integrity sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0= - dependencies: - ms "^2.0.0" - -iconv-lite@^0.6.2: - version "0.6.3" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" @@ -1462,6 +948,11 @@ ignore@^5.2.0: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== +immutable@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.2.1.tgz#8a4025691018c560a40c67e43d698f816edc44d4" + integrity sha512-7WYV7Q5BTs0nlQm7tl92rDYYoyELLKHoDMBKhrxEoiV4mrfVdRz8hzPiYOzH7yWjzoVEamxRuAqhxL2PLRwZYQ== + import-fresh@^3.0.0, import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" @@ -1475,16 +966,6 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" - integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== - -infer-owner@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" - integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== - inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -1493,7 +974,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.3, inherits@~2.0.3: +inherits@2: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -1507,16 +988,6 @@ internal-slot@^1.0.3: has "^1.0.3" side-channel "^1.0.4" -ip@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" - integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= - -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= - is-bigint@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" @@ -1524,6 +995,13 @@ is-bigint@^1.0.1: dependencies: has-bigints "^1.0.1" +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-boolean-object@^1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" @@ -1537,7 +1015,7 @@ is-callable@^1.1.4, is-callable@^1.2.4: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== -is-core-module@^2.2.0, is-core-module@^2.5.0, is-core-module@^2.8.0, is-core-module@^2.8.1: +is-core-module@^2.2.0, is-core-module@^2.8.0, is-core-module@^2.8.1: version "2.8.1" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211" integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA== @@ -1556,23 +1034,13 @@ is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, 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-lambda@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" - integrity sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU= - is-negative-zero@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" @@ -1590,11 +1058,6 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-plain-obj@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" - integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= - is-regex@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" @@ -1622,11 +1085,6 @@ is-symbol@^1.0.2, is-symbol@^1.0.3: dependencies: has-symbols "^1.0.2" -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= - is-weakref@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" @@ -1634,27 +1092,12 @@ is-weakref@^1.0.1: dependencies: call-bind "^1.0.2" -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - 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= -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= - -js-base64@^2.4.3: - version "2.6.4" - resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4" - integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ== - -"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: +"js-tokens@^3.0.0 || ^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== @@ -1666,53 +1109,23 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= - -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" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - json-schema-traverse@^0.4.1: version "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.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-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= -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= - json5@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" - integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + version "1.0.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== dependencies: minimist "^1.2.0" -jsprim@^1.2.2: - 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.3.0" - json-schema "0.4.0" - verror "1.10.0" - "jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz#720b97bfe7d901b927d87c3773637ae8ea48781b" @@ -1721,11 +1134,6 @@ jsprim@^1.2.2: array-includes "^3.1.3" object.assign "^4.1.2" -kind-of@^6.0.3: - 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== - language-subtag-registry@~0.3.2: version "0.3.21" resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz#04ac218bea46f04cb039084602c6da9e788dd45a" @@ -1746,11 +1154,6 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -lines-and-columns@^1.1.6: - 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@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" @@ -1759,23 +1162,11 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lodash@^4.17.11, lodash@^4.17.15, lodash@~4.17.10: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -1790,56 +1181,6 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -make-fetch-happen@^9.1.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz#53085a09e7971433e6765f7971bf63f4e05cb968" - integrity sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg== - dependencies: - agentkeepalive "^4.1.3" - cacache "^15.2.0" - http-cache-semantics "^4.1.0" - http-proxy-agent "^4.0.1" - https-proxy-agent "^5.0.0" - is-lambda "^1.0.1" - lru-cache "^6.0.0" - minipass "^3.1.3" - minipass-collect "^1.0.2" - minipass-fetch "^1.3.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.4" - negotiator "^0.6.2" - promise-retry "^2.0.1" - socks-proxy-agent "^6.0.0" - ssri "^8.0.0" - -map-obj@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" - integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= - -map-obj@^4.0.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" - integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== - -meow@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/meow/-/meow-9.0.0.tgz#cd9510bc5cac9dee7d03c73ee1f9ad959f4ea364" - integrity sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ== - dependencies: - "@types/minimist" "^1.2.0" - camelcase-keys "^6.2.2" - decamelize "^1.2.0" - decamelize-keys "^1.1.0" - hard-rejection "^2.1.0" - minimist-options "4.1.0" - normalize-package-data "^3.0.0" - read-pkg-up "^7.0.1" - redent "^3.0.0" - trim-newlines "^3.0.0" - type-fest "^0.18.0" - yargs-parser "^20.2.3" - merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" @@ -1853,23 +1194,6 @@ micromatch@^4.0.4: braces "^3.0.1" picomatch "^2.2.3" -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-types@^2.1.12, 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" - -min-indent@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" - integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== - minimatch@^3.0.4, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -1877,85 +1201,10 @@ minimatch@^3.0.4, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" -minimatch@~3.0.2: - version "3.0.8" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.8.tgz#5e6a59bd11e2ab0de1cfb843eb2d82e546c321c1" - integrity sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q== - dependencies: - brace-expansion "^1.1.7" - -minimist-options@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" - integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A== - dependencies: - arrify "^1.0.1" - is-plain-obj "^1.1.0" - kind-of "^6.0.3" - 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== - -minipass-collect@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" - integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== - dependencies: - minipass "^3.0.0" - -minipass-fetch@^1.3.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-1.4.1.tgz#d75e0091daac1b0ffd7e9d41629faff7d0c1f1b6" - integrity sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw== - dependencies: - minipass "^3.1.0" - minipass-sized "^1.0.3" - minizlib "^2.0.0" - optionalDependencies: - encoding "^0.1.12" - -minipass-flush@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" - integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== - dependencies: - minipass "^3.0.0" - -minipass-pipeline@^1.2.2, minipass-pipeline@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" - integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== - dependencies: - minipass "^3.0.0" - -minipass-sized@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/minipass-sized/-/minipass-sized-1.0.3.tgz#70ee5a7c5052070afacfbc22977ea79def353b70" - integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== - dependencies: - minipass "^3.0.0" - -minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: - version "3.1.6" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.6.tgz#3b8150aa688a711a1521af5e8779c1d3bb4f45ee" - integrity sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ== - dependencies: - yallist "^4.0.0" - -minizlib@^2.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@^1.0.3, mkdirp@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + version "1.2.7" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" + integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== ms@2.0.0: version "2.0.0" @@ -1967,16 +1216,11 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.0.0, ms@^2.1.1: +ms@^2.1.1: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -nan@^2.13.2: - version "2.15.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" - integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== - nanoid@^3.1.30: version "3.3.1" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35" @@ -1987,11 +1231,6 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -negotiator@^0.6.2: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - next@12.1.0: version "12.1.0" resolved "https://registry.yarnpkg.com/next/-/next-12.1.0.tgz#c33d753b644be92fc58e06e5a214f143da61dd5d" @@ -2029,94 +1268,10 @@ node-fetch@^3.2.10: fetch-blob "^3.1.4" formdata-polyfill "^4.0.10" -node-gyp@^8.4.1: - version "8.4.1" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.4.1.tgz#3d49308fc31f768180957d6b5746845fbd429937" - integrity sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w== - dependencies: - env-paths "^2.2.0" - glob "^7.1.4" - graceful-fs "^4.2.6" - make-fetch-happen "^9.1.0" - nopt "^5.0.0" - npmlog "^6.0.0" - rimraf "^3.0.2" - semver "^7.3.5" - tar "^6.1.2" - which "^2.0.2" - -node-sass@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-7.0.1.tgz#ad4f6bc663de8acc0a9360db39165a1e2620aa72" - integrity sha512-uMy+Xt29NlqKCFdFRZyXKOTqGt+QaKHexv9STj2WeLottnlqZEEWx6Bj0MXNthmFRRdM/YwyNo/8Tr46TOM0jQ== - dependencies: - async-foreach "^0.1.3" - chalk "^4.1.2" - cross-spawn "^7.0.3" - gaze "^1.0.0" - get-stdin "^4.0.1" - glob "^7.0.3" - lodash "^4.17.15" - meow "^9.0.0" - nan "^2.13.2" - node-gyp "^8.4.1" - npmlog "^5.0.0" - request "^2.88.0" - sass-graph "4.0.0" - stdout-stream "^1.4.0" - "true-case-path" "^1.0.2" - -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: - abbrev "1" - -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== - dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - -normalize-package-data@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e" - integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA== - dependencies: - hosted-git-info "^4.0.1" - is-core-module "^2.5.0" - semver "^7.3.4" - validate-npm-package-license "^3.0.1" - -npmlog@^5.0.0: - 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" - -npmlog@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.1.tgz#06f1344a174c06e8de9c6c70834cfba2964bba17" - integrity sha512-BTHDvY6nrRHuRfyjt1MAufLxYdVXZfd099H4+i1f0lPywNQyI4foeNXJRObB/uy+TYqUW0vAD9gbdSOXPst7Eg== - dependencies: - are-we-there-yet "^3.0.0" - console-control-strings "^1.1.0" - gauge "^4.0.0" - set-blocking "^2.0.0" - -oauth-sign@~0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" - integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== +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== object-assign@^4.1.1: version "4.1.1" @@ -2204,13 +1359,6 @@ p-limit@^1.1.0: dependencies: p-try "^1.0.0" -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - p-locate@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" @@ -2218,30 +1366,11 @@ p-locate@^2.0.0: dependencies: p-limit "^1.1.0" -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-map@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" - integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== - dependencies: - aggregate-error "^3.0.0" - p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -2249,26 +1378,11 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parse-json@^5.0.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== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-even-better-errors "^2.3.0" - lines-and-columns "^1.1.6" - path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" @@ -2289,17 +1403,12 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -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= - 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.2.3: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== @@ -2318,24 +1427,6 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -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== - -promise-inflight@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" - integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= - -promise-retry@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" - integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== - dependencies: - err-code "^2.0.2" - retry "^0.12.0" - prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" @@ -2345,31 +1436,16 @@ prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" -psl@^1.1.28: - version "1.8.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" - integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== - -punycode@^2.1.0, punycode@^2.1.1: +punycode@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -qs@~6.5.2: - version "6.5.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" - integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== - queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -quick-lru@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" - integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== - react-dom@17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" @@ -2397,54 +1473,12 @@ react@17.0.2: loose-envify "^1.1.0" object-assign "^4.1.1" -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@^2.0.1: - 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" - -readable-stream@^3.6.0: +readdirp@~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== + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -redent@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" - integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== - dependencies: - indent-string "^4.0.0" - strip-indent "^3.0.0" + picomatch "^2.2.1" regenerator-runtime@^0.13.4: version "0.13.9" @@ -2464,43 +1498,12 @@ regexpp@^3.2.0: resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== -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== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.3" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.5.0" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - -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= - resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -resolve@^1.10.0, resolve@^1.20.0: +resolve@^1.20.0: version "1.22.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== @@ -2517,11 +1520,6 @@ resolve@^2.0.0-next.3: is-core-module "^2.2.0" path-parse "^1.0.6" -retry@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= - reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -2541,30 +1539,14 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -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.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== - -"safer-buffer@>= 2.1.2 < 3.0.0", 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== - -sass-graph@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-4.0.0.tgz#fff8359efc77b31213056dfd251d05dadc74c613" - integrity sha512-WSO/MfXqKH7/TS8RdkCX3lVkPFQzCgbqdGsmSKq6tlPU+GpGEsa/5aW18JqItnqh+lPtcjifqdZ/VmiILkKckQ== +sass@^1.52.3: + version "1.57.1" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.57.1.tgz#dfafd46eb3ab94817145e8825208ecf7281119b5" + integrity sha512-O2+LwLS79op7GI0xZ8fqzF7X2m/m8WFfI02dHOdsK5R2ECeS5F62zrwg/relM1rjSLy7Vd/DiMNIvPrQGsA0jw== dependencies: - glob "^7.0.0" - lodash "^4.17.11" - scss-tokenizer "^0.3.0" - yargs "^17.2.1" + chokidar ">=3.0.0 <4.0.0" + immutable "^4.0.0" + source-map-js ">=0.6.2 <2.0.0" scheduler@^0.20.2: version "0.20.2" @@ -2574,36 +1556,18 @@ scheduler@^0.20.2: loose-envify "^1.1.0" object-assign "^4.1.1" -scss-tokenizer@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.3.0.tgz#ef7edc3bc438b25cd6ffacf1aa5b9ad5813bf260" - integrity sha512-14Zl9GcbBvOT9057ZKjpz5yPOyUWG2ojd9D5io28wHRYsOrs7U95Q+KNL87+32p8rc+LvDpbu/i9ZYjM9Q+FsQ== - dependencies: - js-base64 "^2.4.3" - source-map "^0.7.1" - -"semver@2 || 3 || 4 || 5": - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - 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.4, semver@^7.3.5: +semver@^7.3.5: 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= - shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -2625,112 +1589,16 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" -signal-exit@^3.0.0, signal-exit@^3.0.7: - 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== - slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -smart-buffer@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" - integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== - -socks-proxy-agent@^6.0.0: - version "6.1.1" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz#e664e8f1aaf4e1fb3df945f09e3d94f911137f87" - integrity sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew== - dependencies: - agent-base "^6.0.2" - debug "^4.3.1" - socks "^2.6.1" - -socks@^2.6.1: - version "2.6.2" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.6.2.tgz#ec042d7960073d40d94268ff3bb727dc685f111a" - integrity sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA== - dependencies: - ip "^1.1.5" - smart-buffer "^4.2.0" - -source-map-js@^1.0.1: +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== -source-map@^0.7.1: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== - -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== - 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== - -sshpk@^1.7.0: - 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" - bcrypt-pbkdf "^1.0.0" - dashdash "^1.12.0" - ecc-jsbn "~0.1.1" - getpass "^0.1.1" - jsbn "~0.1.0" - safer-buffer "^2.0.2" - tweetnacl "~0.14.0" - -ssri@^8.0.0, ssri@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" - integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== - dependencies: - minipass "^3.1.1" - -stdout-stream@^1.4.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.1.tgz#5ac174cdd5cd726104aa0c0b2bd83815d8d535de" - integrity sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA== - dependencies: - readable-stream "^2.0.1" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, 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== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string.prototype.matchall@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.6.tgz#5abb5dabc94c7b0ea2380f65ba610b3a544b15fa" @@ -2761,21 +1629,7 @@ string.prototype.trimstart@^1.0.4: call-bind "^1.0.2" define-properties "^1.1.3" -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -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: +strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -2787,13 +1641,6 @@ strip-bom@^3.0.0: resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= -strip-indent@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" - integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== - dependencies: - min-indent "^1.0.0" - strip-json-comments@^3.1.0, 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" @@ -2804,13 +1651,6 @@ styled-jsx@5.0.0: resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.0.0.tgz#816b4b92e07b1786c6b7111821750e0ba4d26e77" integrity sha512-qUqsWoBquEdERe10EW8vLp3jT25s/ssG1/qX5gZ4wu15OZpmSMFI2v+fWlRhLfykA5rFtlJ1ME8A8pm/peV4WA== -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" @@ -2823,18 +1663,6 @@ supports-preserve-symlinks-flag@^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== -tar@^6.0.2, tar@^6.1.2: - 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" - text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -2847,26 +1675,6 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.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== - dependencies: - psl "^1.1.28" - punycode "^2.1.1" - -trim-newlines@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" - integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== - -"true-case-path@^1.0.2": - version "1.0.3" - resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.3.tgz#f813b5a8c86b40da59606722b144e3225799f47d" - integrity sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew== - dependencies: - glob "^7.1.2" - tsconfig-paths@^3.12.0, tsconfig-paths@^3.9.0: version "3.13.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.13.0.tgz#f3e9b8f6876698581d94470c03c95b3a48c0e3d7" @@ -2889,18 +1697,6 @@ tsutils@^3.21.0: dependencies: tslib "^1.8.1" -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= - dependencies: - safe-buffer "^5.0.1" - -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= - type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -2908,26 +1704,11 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" -type-fest@^0.18.0: - version "0.18.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" - integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== - 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.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== - typescript@4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.2.tgz#fe12d2727b708f4eef40f51598b3398baa9611d4" @@ -2943,20 +1724,6 @@ unbox-primitive@^1.0.1: has-symbols "^1.0.2" which-boxed-primitive "^1.0.2" -unique-filename@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" - integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== - dependencies: - unique-slug "^2.0.0" - -unique-slug@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" - integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== - dependencies: - imurmurhash "^0.1.4" - uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -2971,38 +1738,11 @@ use-subscription@1.5.1: dependencies: object-assign "^4.1.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= - -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== - v8-compile-cache@^2.0.3: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== -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" - -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: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - web-streams-polyfill@^3.0.3: version "3.2.0" resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz#a6b74026b38e4885869fb5c589e90b95ccfc7965" @@ -3019,68 +1759,24 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" -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, wide-align@^1.1.5: - 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" - 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@^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" - strip-ansi "^6.0.0" - wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -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@^20.2.3: - 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-parser@^21.0.0: - version "21.0.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.1.tgz#0267f286c877a4f0f728fceb6f8a3e4cb95c6e35" - integrity sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg== - -yargs@^17.2.1: - version "17.3.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.3.1.tgz#da56b28f32e2fd45aefb402ed9c26f42be4c07b9" - integrity sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.0.0" diff --git a/hosting/docker-compose.dev.yaml b/hosting/docker-compose.dev.yaml index 7322b0e8a9..7d8198db73 100644 --- a/hosting/docker-compose.dev.yaml +++ b/hosting/docker-compose.dev.yaml @@ -6,7 +6,8 @@ services: minio-service: container_name: budi-minio-dev restart: on-failure - image: minio/minio + # Last version that supports the "fs" backend + image: minio/minio:RELEASE.2022-10-24T18-35-07Z volumes: - minio_data:/data ports: @@ -25,9 +26,9 @@ services: proxy-service: container_name: budi-nginx-dev restart: on-failure - image: nginx:latest + image: budibase/proxy:latest volumes: - - ./.generated-nginx.dev.conf:/etc/nginx/nginx.conf + - ./nginx.dev.conf:/etc/nginx/templates/nginx.conf.template - ./proxy/error.html:/usr/share/nginx/html/error.html ports: - "${MAIN_PORT}:10000" @@ -36,28 +37,21 @@ services: - couchdb-service extra_hosts: - "host.docker.internal:host-gateway" + environment: + - PROXY_ADDRESS=host.docker.internal couchdb-service: # platform: linux/amd64 - container_name: budi-couchdb-dev + container_name: budi-couchdb3-dev restart: on-failure - image: ibmcom/couchdb3 + image: budibase/couchdb environment: - COUCHDB_PASSWORD=${COUCH_DB_PASSWORD} - COUCHDB_USER=${COUCH_DB_USER} ports: - "${COUCH_DB_PORT}:5984" volumes: - - couchdb3_data:/opt/couchdb/data - - couch-init: - container_name: budi-couchdb-init-dev - image: curlimages/curl - environment: - PUT_CALL: "curl -u ${COUCH_DB_USER}:${COUCH_DB_PASSWORD} -X PUT couchdb-service:5984" - depends_on: - - couchdb-service - command: ["sh","-c","sleep 10 && $${PUT_CALL}/_users && $${PUT_CALL}/_replicator; fg;"] + - couchdb_data:/data redis-service: container_name: budi-redis-dev @@ -70,9 +64,9 @@ services: - redis_data:/data volumes: - couchdb3_data: + couchdb_data: driver: local minio_data: driver: local redis_data: - driver: local + driver: local \ No newline at end of file diff --git a/hosting/docker-compose.yaml b/hosting/docker-compose.yaml index 5b2adc2665..d36937910f 100644 --- a/hosting/docker-compose.yaml +++ b/hosting/docker-compose.yaml @@ -82,6 +82,12 @@ services: environment: - PROXY_RATE_LIMIT_WEBHOOKS_PER_SECOND=10 - PROXY_RATE_LIMIT_API_PER_SECOND=20 + - APPS_UPSTREAM_URL=http://app-service:4002 + - WORKER_UPSTREAM_URL=http://worker-service:4003 + - MINIO_UPSTREAM_URL=http://minio-service:9000 + - COUCHDB_UPSTREAM_URL=http://couchdb-service:5984 + - WATCHTOWER_UPSTREAM_URL=http://watchtower-service:8080 + - RESOLVER=127.0.0.11 depends_on: - minio-service - worker-service diff --git a/hosting/nginx.dev.conf b/hosting/nginx.dev.conf new file mode 100644 index 0000000000..1ecee422cd --- /dev/null +++ b/hosting/nginx.dev.conf @@ -0,0 +1,215 @@ +user nginx; +error_log /var/log/nginx/error.log debug; +pid /var/run/nginx.pid; +worker_processes auto; +worker_rlimit_nofile 33282; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + proxy_set_header Host $host; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for" ' + 'response_time=$upstream_response_time proxy_host=$proxy_host upstream_addr=$upstream_addr'; + + access_log /var/log/nginx/access.log main; + + map $http_upgrade $connection_upgrade { + default "upgrade"; + } + + upstream app-service { + server ${PROXY_ADDRESS}:4001; + keepalive 32; + } + + upstream worker-service { + server ${PROXY_ADDRESS}:4002; + keepalive 32; + } + + upstream builder { + server ${PROXY_ADDRESS}:3000; + keepalive 32; + } + + server { + listen 10000 default_server; + server_name _; + client_max_body_size 1000m; + 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; + } + + location ~ ^/api/(system|admin|global)/ { + proxy_read_timeout 120s; + proxy_connect_timeout 120s; + proxy_send_timeout 120s; + proxy_http_version 1.1; + + proxy_set_header Host $host; + proxy_set_header Connection ""; + + proxy_pass http://worker-service; + } + + location /api/backups/ { + proxy_read_timeout 1800s; + proxy_connect_timeout 1800s; + proxy_send_timeout 1800s; + proxy_pass http://app-service; + proxy_http_version 1.1; + proxy_set_header Connection ""; + } + + location /api/ { + proxy_read_timeout 120s; + proxy_connect_timeout 120s; + proxy_send_timeout 120s; + proxy_http_version 1.1; + + proxy_set_header Host $host; + proxy_set_header Connection ""; + + proxy_pass http://app-service; + } + + location = / { + proxy_read_timeout 120s; + proxy_connect_timeout 120s; + proxy_send_timeout 120s; + proxy_http_version 1.1; + + proxy_set_header Host $host; + proxy_set_header Connection ""; + + proxy_pass http://app-service; + } + + location /app_ { + proxy_read_timeout 120s; + proxy_connect_timeout 120s; + proxy_send_timeout 120s; + proxy_http_version 1.1; + + proxy_set_header Host $host; + proxy_set_header Connection ""; + + proxy_pass http://app-service; + } + + location /app { + proxy_read_timeout 120s; + proxy_connect_timeout 120s; + proxy_send_timeout 120s; + proxy_http_version 1.1; + + proxy_set_header Host $host; + proxy_set_header Connection ""; + + proxy_pass http://app-service; + } + + location /builder { + proxy_read_timeout 120s; + proxy_connect_timeout 120s; + proxy_send_timeout 120s; + proxy_http_version 1.1; + + proxy_set_header Host $host; + proxy_set_header Connection ""; + + proxy_pass http://builder; + rewrite ^/builder(.*)$ /builder/$1 break; + } + + location /builder/ { + proxy_http_version 1.1; + + proxy_set_header Host $host; + 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_read_timeout 120s; + proxy_connect_timeout 120s; + proxy_send_timeout 120s; + + proxy_pass http://builder; + } + + location /vite/ { + proxy_pass http://builder; + proxy_read_timeout 120s; + proxy_connect_timeout 120s; + proxy_send_timeout 120s; + rewrite ^/vite(.*)$ /$1 break; + } + + location /socket/ { + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + proxy_pass http://app-service; + } + + 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_set_header Host $http_host; + + proxy_connect_timeout 300; + proxy_http_version 1.1; + proxy_set_header Connection ""; + chunked_transfer_encoding off; + + proxy_pass http://minio-service:9000; + } + + location /files/signed/ { + 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; + + # IMPORTANT: Signed urls will inspect the host header of the request. + # Normally a signed url will need to be generated with a specified client host in mind. + # To support dynamic hosts, e.g. some unknown self-hosted installation url, + # use a predefined host header. The host 'minio-service' is also used at the time of url signing. + proxy_set_header Host minio-service; + + proxy_connect_timeout 300; + proxy_http_version 1.1; + proxy_set_header Connection ""; + chunked_transfer_encoding off; + + proxy_pass http://minio-service:9000; + rewrite ^/files/signed/(.*)$ /$1 break; + } + + client_header_timeout 60; + client_body_timeout 60; + keepalive_timeout 60; + gzip off; + gzip_comp_level 4; + } +} diff --git a/hosting/nginx.dev.conf.hbs b/hosting/nginx.dev.conf.hbs deleted file mode 100644 index 14c32b1bba..0000000000 --- a/hosting/nginx.dev.conf.hbs +++ /dev/null @@ -1,117 +0,0 @@ -user nginx; -error_log /var/log/nginx/error.log debug; -pid /var/run/nginx.pid; -worker_processes auto; -worker_rlimit_nofile 33282; - -events { - worker_connections 1024; -} - -http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - proxy_set_header Host $host; - - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for" ' - 'response_time=$upstream_response_time proxy_host=$proxy_host upstream_addr=$upstream_addr'; - - access_log /var/log/nginx/access.log main; - - map $http_upgrade $connection_upgrade { - default "upgrade"; - } - - server { - listen 10000 default_server; - server_name _; - client_max_body_size 1000m; - 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; - } - - location ~ ^/api/(system|admin|global)/ { - proxy_pass http://{{ address }}:4002; - } - - location /api/ { - proxy_read_timeout 120s; - proxy_connect_timeout 120s; - proxy_send_timeout 120s; - proxy_pass http://{{ address }}:4001; - } - - location = / { - proxy_pass http://{{ address }}:4001; - } - - location /app_ { - proxy_pass http://{{ address }}:4001; - } - - location /app { - proxy_pass http://{{ address }}:4001; - } - - location /builder { - proxy_pass http://{{ address }}:3000; - rewrite ^/builder(.*)$ /builder/$1 break; - } - - location /builder/ { - proxy_pass http://{{ address }}:3000; - - 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; - } - - location /vite/ { - proxy_pass http://{{ address }}:3000; - rewrite ^/vite(.*)$ /$1 break; - } - - location /socket/ { - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - proxy_set_header Host $host; - proxy_cache_bypass $http_upgrade; - proxy_pass http://{{ address }}:4001; - } - - 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_set_header Host $http_host; - - proxy_connect_timeout 300; - proxy_http_version 1.1; - proxy_set_header Connection ""; - chunked_transfer_encoding off; - - proxy_pass http://minio-service:9000; - } - - client_header_timeout 60; - client_body_timeout 60; - keepalive_timeout 60; - gzip off; - gzip_comp_level 4; - } -} diff --git a/hosting/proxy/10-listen-on-ipv6-by-default.sh b/hosting/proxy/10-listen-on-ipv6-by-default.sh new file mode 100644 index 0000000000..e2e89388a9 --- /dev/null +++ b/hosting/proxy/10-listen-on-ipv6-by-default.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# vim:sw=4:ts=4:et + +set -e + +ME=$(basename $0) +NGINX_CONF_FILE="/etc/nginx/nginx.conf" +DEFAULT_CONF_FILE="/etc/nginx/conf.d/default.conf" + +# check if we have ipv6 available +if [ ! -f "/proc/net/if_inet6" ]; then + # ipv6 not available so delete lines from nginx conf + if [ -f "$NGINX_CONF_FILE" ]; then + sed -i '/listen \[::\]/d' $NGINX_CONF_FILE + fi + if [ -f "$DEFAULT_CONF_FILE" ]; then + sed -i '/listen \[::\]/d' $DEFAULT_CONF_FILE + fi + echo "$ME: info: ipv6 not available so delete lines from nginx conf" +else + echo "$ME: info: ipv6 is available so no need to delete lines from nginx conf" +fi + +exit 0 diff --git a/hosting/proxy/80-listen-on-ipv6-by-default.sh b/hosting/proxy/80-listen-on-ipv6-by-default.sh new file mode 100644 index 0000000000..1d62732ea1 --- /dev/null +++ b/hosting/proxy/80-listen-on-ipv6-by-default.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# vim:sw=4:ts=4:et + +set -e + +ME=$(basename $0) +NGINX_CONF_FILE="/etc/nginx/nginx.conf" +DEFAULT_CONF_FILE="/etc/nginx/conf.d/default.conf" + +# check if we have ipv6 available +if [ ! -f "/proc/net/if_inet6" ]; then + # ipv6 not available so delete lines from nginx conf + if [ -f "$NGINX_CONF_FILE" ]; then + sed -i '/listen \[::\]/d' $NGINX_CONF_FILE + fi + if [ -f "$DEFAULT_CONF_FILE" ]; then + sed -i '/listen \[::\]/d' $DEFAULT_CONF_FILE + fi + echo "$ME: info: ipv6 not available so delete lines from nginx conf" +else + echo "$ME: info: ipv6 is available so no need to delete lines from nginx conf" +fi + +exit 0 \ No newline at end of file diff --git a/hosting/proxy/Dockerfile b/hosting/proxy/Dockerfile index 298762aaf1..42327be087 100644 --- a/hosting/proxy/Dockerfile +++ b/hosting/proxy/Dockerfile @@ -4,11 +4,23 @@ FROM nginx:latest # use the default nginx behaviour for *.template files which are processed with envsubst # override the output dir to output directly to /etc/nginx instead of /etc/nginx/conf.d ENV NGINX_ENVSUBST_OUTPUT_DIR=/etc/nginx -COPY .generated-nginx.prod.conf /etc/nginx/templates/nginx.conf.template +COPY nginx.prod.conf /etc/nginx/templates/nginx.conf.template + +# IPv6 removal needs to happen after envsubst +RUN rm -rf /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh +COPY 80-listen-on-ipv6-by-default.sh /docker-entrypoint.d/80-listen-on-ipv6-by-default.sh +RUN chmod +x /docker-entrypoint.d/80-listen-on-ipv6-by-default.sh # Error handling COPY error.html /usr/share/nginx/html/error.html # Default environment ENV PROXY_RATE_LIMIT_WEBHOOKS_PER_SECOND=10 -ENV PROXY_RATE_LIMIT_API_PER_SECOND=20 \ No newline at end of file +ENV PROXY_RATE_LIMIT_API_PER_SECOND=20 +# Use docker-compose values as defaults for backwards compatibility +ENV APPS_UPSTREAM_URL=http://app-service:4002 +ENV WORKER_UPSTREAM_URL=http://worker-service:4003 +ENV MINIO_UPSTREAM_URL=http://minio-service:9000 +ENV COUCHDB_UPSTREAM_URL=http://couchdb-service:5984 +ENV WATCHTOWER_UPSTREAM_URL=http://watchtower-service:8080 +ENV RESOLVER=127.0.0.11 diff --git a/hosting/nginx.prod.conf.hbs b/hosting/proxy/nginx.prod.conf similarity index 58% rename from hosting/nginx.prod.conf.hbs rename to hosting/proxy/nginx.prod.conf index d57a822f3f..21b337deae 100644 --- a/hosting/nginx.prod.conf.hbs +++ b/hosting/proxy/nginx.prod.conf @@ -23,7 +23,7 @@ http { tcp_nodelay on; server_tokens off; types_hash_max_size 2048; - resolver {{ resolver }} valid=10s ipv6=off; + resolver ${RESOLVER} valid=10s ipv6=off; # buffering client_header_buffer_size 1k; @@ -51,11 +51,11 @@ http { proxy_buffering off; 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_script "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.budibase.net 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 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.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_connect "connect-src 'self' https://*.budibase.net 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 https://api.github.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:"; @@ -76,46 +76,65 @@ http { 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 }}; - set $worker {{ worker }}; - set $minio {{ minio }}; - set $couchdb {{ couchdb }}; - {{#if watchtower}} - set $watchtower {{ watchtower }}; - {{/if}} + set $apps ${APPS_UPSTREAM_URL}; + set $worker ${WORKER_UPSTREAM_URL}; + set $minio ${MINIO_UPSTREAM_URL}; + set $couchdb ${COUCHDB_UPSTREAM_URL}; + set $watchtower ${WATCHTOWER_UPSTREAM_URL}; location /app { - proxy_pass http://$apps:4002; + proxy_pass $apps; } location = / { - proxy_pass http://$apps:4002; + proxy_pass $apps; } - {{#if watchtower}} location = /v1/update { - proxy_pass http://$watchtower:8080; + proxy_pass $watchtower; } - {{/if}} 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://$apps:4002; + proxy_set_header Host $host; + + proxy_pass $apps; } location ~ ^/api/(system|admin|global)/ { - proxy_pass http://$worker:4003; + proxy_set_header Host $host; + proxy_pass $worker; } location /worker/ { - proxy_pass http://$worker:4003; + proxy_set_header Host $host; + proxy_pass $worker; rewrite ^/worker/(.*)$ /$1 break; } + location /api/backups/ { + # calls to export apps are limited + limit_req zone=ratelimit burst=20 nodelay; + + # 1800s timeout for app export requests + proxy_read_timeout 1800s; + proxy_connect_timeout 1800s; + proxy_send_timeout 1800s; + + 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 $apps; + } + location /api/ { # calls to the API are rate limited with bursting limit_req zone=ratelimit burst=20 nodelay; @@ -130,8 +149,9 @@ http { 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_set_header Host $host; - proxy_pass http://$apps:4002; + proxy_pass $apps; } location /api/webhooks/ { @@ -149,12 +169,13 @@ http { 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_set_header Host $host; - proxy_pass http://$apps:4002; + proxy_pass $apps; } location /db/ { - proxy_pass http://$couchdb:5984; + proxy_pass $couchdb; rewrite ^/db/(.*)$ /$1 break; } @@ -164,19 +185,41 @@ http { proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; - proxy_pass http://$apps:4002; + proxy_pass $apps; } 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_set_header Host $http_host; proxy_connect_timeout 300; proxy_http_version 1.1; proxy_set_header Connection ""; chunked_transfer_encoding off; - proxy_pass http://$minio:9000; + + proxy_pass $minio; + } + + location /files/signed/ { + 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; + + # IMPORTANT: Signed urls will inspect the host header of the request. + # Normally a signed url will need to be generated with a specified client host in mind. + # To support dynamic hosts, e.g. some unknown self-hosted installation url, + # use a predefined host header. The host 'minio-service' is also used at the time of url signing. + proxy_set_header Host minio-service; + + proxy_connect_timeout 300; + proxy_http_version 1.1; + proxy_set_header Connection ""; + chunked_transfer_encoding off; + + proxy_pass $minio; + rewrite ^/files/signed/(.*)$ /$1 break; } client_header_timeout 60; diff --git a/hosting/scripts/build-target-paths.sh b/hosting/scripts/build-target-paths.sh index c974d9a304..67e1765ca8 100644 --- a/hosting/scripts/build-target-paths.sh +++ b/hosting/scripts/build-target-paths.sh @@ -4,6 +4,7 @@ echo ${TARGETBUILD} > /buildtarget.txt if [[ "${TARGETBUILD}" = "aas" ]]; then # Azure AppService uses /home for persisent data & SSH on port 2222 DATA_DIR=/home + WEBSITES_ENABLE_APP_SERVICE_STORAGE=true mkdir -p $DATA_DIR/{search,minio,couch} mkdir -p $DATA_DIR/couch/{dbs,views} chown -R couchdb:couchdb $DATA_DIR/couch/ diff --git a/hosting/single/Dockerfile b/hosting/single/Dockerfile index f34290f627..5127db9897 100644 --- a/hosting/single/Dockerfile +++ b/hosting/single/Dockerfile @@ -19,8 +19,8 @@ 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 +ARG TARGETARCH +ENV TARGETARCH $TARGETARCH #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 @@ -61,7 +61,8 @@ 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 + touch /var/run/nginx.pid && \ + usermod -a -G tty www-data WORKDIR / RUN mkdir -p scripts/integrations/oracle diff --git a/hosting/single/nginx/nginx-default-site.conf b/hosting/single/nginx/nginx-default-site.conf index bd89e21251..3903c0647d 100644 --- a/hosting/single/nginx/nginx-default-site.conf +++ b/hosting/single/nginx/nginx-default-site.conf @@ -2,7 +2,8 @@ server { listen 80 default_server; listen [::]:80 default_server; server_name _; - + error_log /dev/stderr warn; + access_log /dev/stdout main; client_max_body_size 1000m; ignore_invalid_headers off; proxy_buffering off; @@ -43,6 +44,24 @@ server { rewrite ^/worker/(.*)$ /$1 break; } + location /api/backups/ { + # calls to export apps are limited + limit_req zone=ratelimit burst=20 nodelay; + + # 1800s timeout for app export requests + proxy_read_timeout 1800s; + proxy_connect_timeout 1800s; + proxy_send_timeout 1800s; + + 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/ { # calls to the API are rate limited with bursting limit_req zone=ratelimit burst=20 nodelay; @@ -76,15 +95,37 @@ server { } 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_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_set_header Host $http_host; - 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; + 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; + } + + location /files/signed/ { + 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; + + # IMPORTANT: Signed urls will inspect the host header of the request. + # Normally a signed url will need to be generated with a specified client host in mind. + # To support dynamic hosts, e.g. some unknown self-hosted installation url, + # use a predefined host header. The host 'minio-service' is also used at the time of url signing. + proxy_set_header Host minio-service; + + 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; + rewrite ^/files/signed/(.*)$ /$1 break; } client_header_timeout 60; diff --git a/hosting/single/nginx/nginx.conf b/hosting/single/nginx/nginx.conf index 1e5d1c20d2..c86e3877f3 100644 --- a/hosting/single/nginx/nginx.conf +++ b/hosting/single/nginx/nginx.conf @@ -1,5 +1,5 @@ user www-data www-data; -error_log /var/log/nginx/error.log; +error_log /dev/stderr warn; pid /var/run/nginx.pid; worker_processes auto; worker_rlimit_nofile 8192; diff --git a/hosting/single/runner.sh b/hosting/single/runner.sh index e02b33d771..3a28cd6e4f 100644 --- a/hosting/single/runner.sh +++ b/hosting/single/runner.sh @@ -10,7 +10,7 @@ declare -a DOCKER_VARS=("APP_PORT" "APPS_URL" "ARCHITECTURE" "BUDIBASE_ENVIRONME [[ -z "${MINIO_URL}" ]] && export MINIO_URL=http://localhost:9000 [[ -z "${NODE_ENV}" ]] && export NODE_ENV=production [[ -z "${POSTHOG_TOKEN}" ]] && export POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU -[[ -z "${TENANT_FEATURE_FLAGS}" ]] && export TENANT_FEATURE_FLAGS="*:LICENSING,*:USER_GROUPS" +[[ -z "${TENANT_FEATURE_FLAGS}" ]] && export TENANT_FEATURE_FLAGS="*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR" [[ -z "${ACCOUNT_PORTAL_URL}" ]] && export ACCOUNT_PORTAL_URL=https://account.budibase.app [[ -z "${REDIS_URL}" ]] && export REDIS_URL=localhost:6379 [[ -z "${SELF_HOSTED}" ]] && export SELF_HOSTED=1 @@ -18,13 +18,24 @@ declare -a DOCKER_VARS=("APP_PORT" "APPS_URL" "ARCHITECTURE" "BUDIBASE_ENVIRONME [[ -z "${WORKER_URL}" ]] && export WORKER_URL=http://localhost:4002 [[ -z "${APPS_URL}" ]] && export APPS_URL=http://localhost:4001 # export CUSTOM_DOMAIN=budi001.custom.com + # Azure App Service customisations if [[ "${TARGETBUILD}" = "aas" ]]; then DATA_DIR=/home + WEBSITES_ENABLE_APP_SERVICE_STORAGE=true /etc/init.d/ssh start else DATA_DIR=${DATA_DIR:-/data} fi +mkdir -p ${DATA_DIR} +# Mount NFS or GCP Filestore if env vars exist for it +if [[ ! -z ${FILESHARE_IP} && ! -z ${FILESHARE_NAME} ]]; then + echo "Mounting NFS share" + apt update && apt install -y nfs-common nfs-kernel-server + echo "Mount file share ${FILESHARE_IP}:/${FILESHARE_NAME} to ${DATA_DIR}" + mount -o nolock ${FILESHARE_IP}:/${FILESHARE_NAME} ${DATA_DIR} + echo "Mounting result: $?" +fi if [ -f "${DATA_DIR}/.env" ]; then # Read in the .env file and export the variables @@ -65,9 +76,9 @@ mkdir -p ${DATA_DIR}/couch/{dbs,views} mkdir -p ${DATA_DIR}/minio mkdir -p ${DATA_DIR}/search chown -R couchdb:couchdb ${DATA_DIR}/couch -redis-server --requirepass $REDIS_PASSWORD & -/opt/clouseau/bin/clouseau & -/minio/minio server ${DATA_DIR}/minio & +redis-server --requirepass $REDIS_PASSWORD > /dev/stdout 2>&1 & +/opt/clouseau/bin/clouseau > /dev/stdout 2>&1 & +/minio/minio server --console-address ":9001" ${DATA_DIR}/minio > /dev/stdout 2>&1 & /docker-entrypoint.sh /opt/couchdb/bin/couchdb & /etc/init.d/nginx restart if [[ ! -z "${CUSTOM_DOMAIN}" ]]; then @@ -76,16 +87,18 @@ if [[ ! -z "${CUSTOM_DOMAIN}" ]]; then chmod +x /etc/cron.d/certificate-renew # Request the certbot certificate /app/letsencrypt/certificate-request.sh ${CUSTOM_DOMAIN} + /etc/init.d/nginx restart fi -/etc/init.d/nginx restart pushd app -pm2 start --name app "yarn run:docker" +pm2 start -l /dev/stdout --name app "yarn run:docker" popd pushd worker -pm2 start --name worker "yarn run:docker" +pm2 start -l /dev/stdout --name worker "yarn run:docker" popd sleep 10 +echo "curl to couchdb endpoints" curl -X PUT ${COUCH_DB_URL}/_users curl -X PUT ${COUCH_DB_URL}/_replicator +echo "end of runner.sh, sleeping ..." sleep infinity diff --git a/lerna.json b/lerna.json index 3e4c32deb3..0377c73101 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.0.22", + "version": "2.3.16", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/package.json b/package.json index 579e86802e..6290930269 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,7 @@ "private": true, "devDependencies": { "@rollup/plugin-json": "^4.0.2", - "@types/mongodb": "3.6.3", - "@typescript-eslint/parser": "4.28.0", + "@typescript-eslint/parser": "5.45.0", "babel-eslint": "^10.0.3", "eslint": "^7.28.0", "eslint-plugin-cypress": "^2.11.3", @@ -19,13 +18,14 @@ "rimraf": "^3.0.2", "rollup-plugin-replace": "^2.2.0", "svelte": "^3.38.2", - "typescript": "4.5.5" + "typescript": "4.7.3" }, "scripts": { "setup": "node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev", "bootstrap": "lerna bootstrap && lerna link && ./scripts/link-dependencies.sh", "build": "lerna run build", "build:dev": "lerna run prebuild && tsc --build --watch --preserveWatchOutput", + "build:backend": "lerna run build --ignore @budibase/client --ignore @budibase/bbui --ignore @budibase/builder --ignore @budibase/cli", "build:sdk": "lerna run build:sdk", "deps:circular": "madge packages/server/dist/index.js packages/worker/src/index.ts packages/backend-core/dist/src/index.js packages/cli/src/index.js --circular", "release": "lerna publish ${RELEASE_VERSION_TYPE:-patch} --yes --force-publish && yarn release:pro", @@ -45,26 +45,18 @@ "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:eslint": "eslint packages && eslint qa-core", + "lint:prettier": "prettier --check \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\" && prettier --check \"qa-core/**/*.{js,ts,svelte}\"", "lint": "yarn run lint:eslint && yarn run lint:prettier", "lint:fix:eslint": "eslint --fix packages qa-core", "lint:fix:prettier": "prettier --write \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\" && prettier --write \"qa-core/**/*.{js,ts,svelte}\"", "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": "lerna run build:docker && npm run build:docker:proxy && 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:develop": "node scripts/pinVersions && lerna run build:docker && npm run build:docker:proxy && 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 .", @@ -76,8 +68,8 @@ "env:multi:disable": "lerna run env:multi:disable", "env:selfhost:enable": "lerna run env:selfhost:enable", "env:selfhost:disable": "lerna run env:selfhost:disable", - "env:localdomain:enable": "lerna run env:localdomain:enable", - "env:localdomain:disable": "lerna run env:localdomain:disable", + "env:localdomain:enable": "./scripts/localdomain.sh enable", + "env:localdomain:disable": "./scripts/localdomain.sh disable", "env:account:enable": "lerna run env:account:enable", "env:account:disable": "lerna run env:account:disable", "mode:self": "yarn env:selfhost:enable && yarn env:multi:disable && yarn env:account:disable", diff --git a/packages/backend-core/__mocks__/aws-sdk.ts b/packages/backend-core/__mocks__/aws-sdk.ts new file mode 100644 index 0000000000..b8d91dbaa9 --- /dev/null +++ b/packages/backend-core/__mocks__/aws-sdk.ts @@ -0,0 +1,18 @@ +const mockS3 = { + headBucket: jest.fn().mockReturnThis(), + deleteObject: jest.fn().mockReturnThis(), + deleteObjects: jest.fn().mockReturnThis(), + createBucket: jest.fn().mockReturnThis(), + listObject: jest.fn().mockReturnThis(), + getSignedUrl: jest.fn((operation: string, params: any) => { + return `http://s3.example.com/${params.Bucket}/${params.Key}` + }), + promise: jest.fn().mockReturnThis(), + catch: jest.fn(), +} + +const AWS = { + S3: jest.fn(() => mockS3), +} + +export default AWS diff --git a/packages/backend-core/accounts.js b/packages/backend-core/accounts.js deleted file mode 100644 index 47ad03456a..0000000000 --- a/packages/backend-core/accounts.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require("./src/cloud/accounts") diff --git a/packages/backend-core/auth.js b/packages/backend-core/auth.js deleted file mode 100644 index bbfe3d41dd..0000000000 --- a/packages/backend-core/auth.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require("./src/auth") diff --git a/packages/backend-core/cache.js b/packages/backend-core/cache.js deleted file mode 100644 index c8bd3c9b6f..0000000000 --- a/packages/backend-core/cache.js +++ /dev/null @@ -1,9 +0,0 @@ -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/constants.js b/packages/backend-core/constants.js deleted file mode 100644 index 4abb7703db..0000000000 --- a/packages/backend-core/constants.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require("./src/constants") diff --git a/packages/backend-core/context.js b/packages/backend-core/context.js deleted file mode 100644 index aaa0f56f92..0000000000 --- a/packages/backend-core/context.js +++ /dev/null @@ -1,22 +0,0 @@ -const { - getAppDB, - getDevAppDB, - getProdAppDB, - getAppId, - updateAppId, - doInAppContext, - doInTenant, -} = require("./src/context") - -const identity = require("./src/context/identity") - -module.exports = { - getAppDB, - getDevAppDB, - getProdAppDB, - getAppId, - updateAppId, - doInAppContext, - doInTenant, - identity, -} diff --git a/packages/backend-core/db.js b/packages/backend-core/db.js deleted file mode 100644 index 0d2869d9f1..0000000000 --- a/packages/backend-core/db.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - ...require("./src/db/utils"), - ...require("./src/db/constants"), - ...require("./src/db"), - ...require("./src/db/views"), - ...require("./src/db/pouch"), -} diff --git a/packages/backend-core/deprovision.js b/packages/backend-core/deprovision.js deleted file mode 100644 index 672da214ff..0000000000 --- a/packages/backend-core/deprovision.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require("./src/context/deprovision") diff --git a/packages/backend-core/encryption.js b/packages/backend-core/encryption.js deleted file mode 100644 index 4ccb6e3a99..0000000000 --- a/packages/backend-core/encryption.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require("./src/security/encryption") diff --git a/packages/backend-core/jest.config.ts b/packages/backend-core/jest.config.ts new file mode 100644 index 0000000000..e5993f6596 --- /dev/null +++ b/packages/backend-core/jest.config.ts @@ -0,0 +1,24 @@ +import { Config } from "@jest/types" + +const config: Config.InitialOptions = { + preset: "ts-jest", + testEnvironment: "node", + setupFiles: ["./tests/jestSetup.ts"], + collectCoverageFrom: ["src/**/*.{js,ts}"], + coverageReporters: ["lcov", "json", "clover"], + transform: { + "^.+\\.ts?$": "@swc/jest", + }, +} + +if (!process.env.CI) { + // use sources when not in CI + config.moduleNameMapper = { + "@budibase/types": "/../types/src", + "^axios.*$": "/node_modules/axios/lib/axios.js", + } +} else { + console.log("Running tests with compiled dependency sources") +} + +export default config diff --git a/packages/backend-core/logging.js b/packages/backend-core/logging.js deleted file mode 100644 index da40fe3100..0000000000 --- a/packages/backend-core/logging.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require("./src/logging") diff --git a/packages/backend-core/middleware.js b/packages/backend-core/middleware.js deleted file mode 100644 index 30fec96239..0000000000 --- a/packages/backend-core/middleware.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require("./src/middleware") diff --git a/packages/backend-core/migrations.js b/packages/backend-core/migrations.js deleted file mode 100644 index 2de19ebf65..0000000000 --- a/packages/backend-core/migrations.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require("./src/migrations") diff --git a/packages/backend-core/objectStore.js b/packages/backend-core/objectStore.js deleted file mode 100644 index 3ee433f224..0000000000 --- a/packages/backend-core/objectStore.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - ...require("./src/objectStore"), - ...require("./src/objectStore/utils"), -} diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index 7f8f6d04f7..455436ff6a 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/backend-core", - "version": "2.0.22", + "version": "2.3.16", "description": "Budibase backend core libraries used in server and worker", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", @@ -15,27 +15,32 @@ "prebuild": "rimraf dist/", "prepack": "cp package.json dist", "build": "tsc -p tsconfig.build.json", + "build:pro": "../../scripts/pro/build.sh", + "postbuild": "yarn run build:pro", "build:dev": "yarn prebuild && tsc --build --watch --preserveWatchOutput", - "test": "jest", + "test": "jest --coverage --maxWorkers=2", "test:watch": "jest --watchAll" }, "dependencies": { - "@budibase/types": "^2.0.22", + "@budibase/nano": "10.1.1", + "@budibase/types": "^2.3.16", "@shopify/jest-koa-mocks": "5.0.1", "@techpass/passport-openidconnect": "0.3.2", + "aws-cloudfront-sign": "2.2.0", "aws-sdk": "2.1030.0", "bcrypt": "5.0.1", "bcryptjs": "2.4.3", + "bull": "4.10.1", + "correlation-id": "4.0.0", "dotenv": "16.0.1", "emitter-listener": "1.1.2", "ioredis": "4.28.0", "joi": "17.6.0", - "jsonwebtoken": "8.5.1", + "jsonwebtoken": "9.0.0", "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", @@ -51,34 +56,33 @@ "uuid": "8.3.2", "zlib": "1.0.5" }, - "jest": { - "preset": "ts-jest", - "testEnvironment": "node", - "moduleNameMapper": { - "@budibase/types": "/../types/src" - }, - "setupFiles": [ - "./scripts/jestSetup.ts" - ] - }, "devDependencies": { + "@swc/core": "^1.3.25", + "@swc/jest": "^0.2.24", + "@types/chance": "1.1.3", + "@types/ioredis": "4.28.0", "@types/jest": "27.5.1", - "@types/koa": "2.0.52", + "@types/koa": "2.13.4", + "@types/koa-pino-logger": "3.0.0", "@types/lodash": "4.14.180", "@types/node": "14.18.20", "@types/node-fetch": "2.6.1", + "@types/pino-http": "5.8.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", + "chance": "1.1.8", "ioredis-mock": "5.8.0", - "jest": "27.5.1", - "koa": "2.7.0", + "jest": "28.1.1", + "koa": "2.13.4", "nodemon": "2.0.16", "pouchdb-adapter-memory": "7.2.2", "timekeeper": "2.2.0", - "ts-jest": "27.1.5", + "ts-jest": "28.0.4", + "ts-node": "10.8.1", + "tsconfig-paths": "4.0.0", "typescript": "4.7.3" }, "gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc" diff --git a/packages/backend-core/permissions.js b/packages/backend-core/permissions.js deleted file mode 100644 index 42f37c9c7e..0000000000 --- a/packages/backend-core/permissions.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require("./src/security/permissions") diff --git a/packages/backend-core/plugins.js b/packages/backend-core/plugins.js deleted file mode 100644 index 018e214dcb..0000000000 --- a/packages/backend-core/plugins.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - ...require("./src/plugin"), -} diff --git a/packages/backend-core/plugins.ts b/packages/backend-core/plugins.ts new file mode 100644 index 0000000000..33354eaf64 --- /dev/null +++ b/packages/backend-core/plugins.ts @@ -0,0 +1 @@ +export * from "./src/plugin" diff --git a/packages/backend-core/redis.js b/packages/backend-core/redis.js deleted file mode 100644 index 1f7a48540a..0000000000 --- a/packages/backend-core/redis.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - Client: require("./src/redis"), - utils: require("./src/redis/utils"), - clients: require("./src/redis/init"), -} diff --git a/packages/backend-core/roles.js b/packages/backend-core/roles.js deleted file mode 100644 index 158bcdb6b8..0000000000 --- a/packages/backend-core/roles.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require("./src/security/roles") diff --git a/packages/backend-core/scripts/jestSetup.ts b/packages/backend-core/scripts/jestSetup.ts deleted file mode 100644 index 1e86649a24..0000000000 --- a/packages/backend-core/scripts/jestSetup.ts +++ /dev/null @@ -1,12 +0,0 @@ -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/sessions.js b/packages/backend-core/sessions.js deleted file mode 100644 index c07efa2380..0000000000 --- a/packages/backend-core/sessions.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require("./src/security/sessions") diff --git a/packages/backend-core/src/auth.ts b/packages/backend-core/src/auth/auth.ts similarity index 76% rename from packages/backend-core/src/auth.ts rename to packages/backend-core/src/auth/auth.ts index 23873b84e7..75e425bd0f 100644 --- a/packages/backend-core/src/auth.ts +++ b/packages/backend-core/src/auth/auth.ts @@ -1,16 +1,14 @@ -const passport = require("koa-passport") +const _passport = require("koa-passport") const LocalStrategy = require("passport-local").Strategy const JwtStrategy = require("passport-jwt").Strategy -import { getGlobalDB } from "./tenancy" +import { getGlobalDB } from "../tenancy" const refresh = require("passport-oauth2-refresh") -import { Configs } from "./constants" -import { getScopedConfig } from "./db/utils" +import { Config } from "../constants" +import { getScopedConfig } from "../db" import { - jwt, + jwt as jwtPassport, local, authenticated, - google, - oidc, auditLog, tenancy, authError, @@ -21,17 +19,41 @@ import { builderOnly, builderOrAdmin, joiValidator, -} from "./middleware" -import { invalidateUser } from "./cache/user" + oidc, + google, +} from "../middleware" +import { invalidateUser } from "../cache/user" import { User } from "@budibase/types" +import { logAlert } from "../logging" +export { + auditLog, + authError, + internalApi, + ssoCallbackUrl, + adminOnly, + builderOnly, + builderOrAdmin, + joiValidator, + google, + oidc, +} from "../middleware" +export const buildAuthMiddleware = authenticated +export const buildTenancyMiddleware = tenancy +export const buildCsrfMiddleware = csrf +export const passport = _passport +export const jwt = require("jsonwebtoken") // Strategies -passport.use(new LocalStrategy(local.options, local.authenticate)) -passport.use(new JwtStrategy(jwt.options, jwt.authenticate)) +_passport.use(new LocalStrategy(local.options, local.authenticate)) +if (jwtPassport.options.secretOrKey) { + _passport.use(new JwtStrategy(jwtPassport.options, jwtPassport.authenticate)) +} else { + logAlert("No JWT Secret supplied, cannot configure JWT strategy") +} -passport.serializeUser((user: User, done: any) => done(null, user)) +_passport.serializeUser((user: User, done: any) => done(null, user)) -passport.deserializeUser(async (user: User, done: any) => { +_passport.deserializeUser(async (user: User, done: any) => { const db = getGlobalDB() try { @@ -71,7 +93,7 @@ async function refreshOIDCAccessToken( return new Promise(resolve => { refresh.requestNewAccessToken( - Configs.OIDC, + Config.OIDC, refreshToken, (err: any, accessToken: string, refreshToken: any, params: any) => { resolve({ err, accessToken, refreshToken, params }) @@ -101,7 +123,7 @@ async function refreshGoogleAccessToken( return new Promise(resolve => { refresh.requestNewAccessToken( - Configs.GOOGLE, + Config.GOOGLE, refreshToken, (err: any, accessToken: string, refreshToken: string, params: any) => { resolve({ err, accessToken, refreshToken, params }) @@ -110,7 +132,7 @@ async function refreshGoogleAccessToken( }) } -async function refreshOAuthToken( +export async function refreshOAuthToken( refreshToken: string, configType: string, configId: string @@ -124,7 +146,7 @@ async function refreshOAuthToken( let chosenConfig = {} let refreshResponse - if (configType === Configs.OIDC) { + if (configType === Config.OIDC) { // configId - retrieved from cookie. chosenConfig = config.configs.filter((c: any) => c.uuid === configId)[0] if (!chosenConfig) { @@ -147,7 +169,7 @@ async function refreshOAuthToken( return refreshResponse } -async function updateUserOAuth(userId: string, oAuthConfig: any) { +export async function updateUserOAuth(userId: string, oAuthConfig: any) { const details = { accessToken: oAuthConfig.accessToken, refreshToken: oAuthConfig.refreshToken, @@ -174,23 +196,3 @@ async function updateUserOAuth(userId: string, oAuthConfig: any) { console.error("Could not update OAuth details for current user", e) } } - -export = { - buildAuthMiddleware: authenticated, - passport, - google, - oidc, - jwt: require("jsonwebtoken"), - buildTenancyMiddleware: tenancy, - auditLog, - authError, - buildCsrfMiddleware: csrf, - internalApi, - refreshOAuthToken, - updateUserOAuth, - ssoCallbackUrl, - adminOnly, - builderOnly, - builderOrAdmin, - joiValidator, -} diff --git a/packages/backend-core/src/auth/index.ts b/packages/backend-core/src/auth/index.ts new file mode 100644 index 0000000000..306751af96 --- /dev/null +++ b/packages/backend-core/src/auth/index.ts @@ -0,0 +1 @@ +export * from "./auth" diff --git a/packages/backend-core/src/cache/appMetadata.js b/packages/backend-core/src/cache/appMetadata.ts similarity index 79% rename from packages/backend-core/src/cache/appMetadata.js rename to packages/backend-core/src/cache/appMetadata.ts index a7ff0d2fc1..d24c4a3140 100644 --- a/packages/backend-core/src/cache/appMetadata.js +++ b/packages/backend-core/src/cache/appMetadata.ts @@ -1,6 +1,6 @@ -const redis = require("../redis/init") -const { doWithDB } = require("../db") -const { DocumentType } = require("../db/constants") +import { getAppClient } from "../redis/init" +import { doWithDB, DocumentType } from "../db" +import { Database } from "@budibase/types" const AppState = { INVALID: "invalid", @@ -10,17 +10,17 @@ const EXPIRY_SECONDS = 3600 /** * The default populate app metadata function */ -const populateFromDB = async appId => { +async function populateFromDB(appId: string) { return doWithDB( appId, - db => { + (db: Database) => { return db.get(DocumentType.APP_METADATA) }, { skip_setup: true } ) } -const isInvalid = metadata => { +function isInvalid(metadata?: { state: string }) { return !metadata || metadata.state === AppState.INVALID } @@ -31,15 +31,15 @@ const isInvalid = metadata => { * @param {string} appId the id of the app to get metadata from. * @returns {object} the app metadata. */ -exports.getAppMetadata = async appId => { - const client = await redis.getAppClient() +export async function getAppMetadata(appId: string) { + const client = await getAppClient() // try cache let metadata = await client.get(appId) if (!metadata) { - let expiry = EXPIRY_SECONDS + let expiry: number | undefined = EXPIRY_SECONDS try { metadata = await populateFromDB(appId) - } catch (err) { + } catch (err: any) { // app DB left around, but no metadata, it is invalid if (err && err.status === 404) { metadata = { state: AppState.INVALID } @@ -74,11 +74,11 @@ exports.getAppMetadata = async appId => { * @param newMetadata {object|undefined} optional - can simply provide the new metadata to update with. * @return {Promise} will respond with success when cache is updated. */ -exports.invalidateAppMetadata = async (appId, newMetadata = null) => { +export async function invalidateAppMetadata(appId: string, newMetadata?: any) { if (!appId) { throw "Cannot invalidate if no app ID provided." } - const client = await redis.getAppClient() + const client = await getAppClient() await client.delete(appId) if (newMetadata) { await client.store(appId, newMetadata, EXPIRY_SECONDS) diff --git a/packages/backend-core/src/cache/base/index.ts b/packages/backend-core/src/cache/base/index.ts index f3216531f4..264984c6a5 100644 --- a/packages/backend-core/src/cache/base/index.ts +++ b/packages/backend-core/src/cache/base/index.ts @@ -1,16 +1,16 @@ import { getTenantId } from "../../context" -import redis from "../../redis/init" -import RedisWrapper from "../../redis" +import * as redis from "../../redis/init" +import { Client } from "../../redis" function generateTenantKey(key: string) { const tenantId = getTenantId() return `${key}:${tenantId}` } -export = class BaseCache { - client: RedisWrapper | undefined +export default class BaseCache { + client: Client | undefined - constructor(client: RedisWrapper | undefined = undefined) { + constructor(client: Client | undefined = undefined) { this.client = client } diff --git a/packages/backend-core/src/cache/generic.js b/packages/backend-core/src/cache/generic.js deleted file mode 100644 index 26ef0c6bb0..0000000000 --- a/packages/backend-core/src/cache/generic.js +++ /dev/null @@ -1,30 +0,0 @@ -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", - EVENTS_RATE_LIMIT: "eventsRateLimit", -} - -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/generic.ts b/packages/backend-core/src/cache/generic.ts new file mode 100644 index 0000000000..7cd5d6227f --- /dev/null +++ b/packages/backend-core/src/cache/generic.ts @@ -0,0 +1,30 @@ +const BaseCache = require("./base") + +const GENERIC = new BaseCache.default() + +export enum CacheKey { + CHECKLIST = "checklist", + INSTALLATION = "installation", + ANALYTICS_ENABLED = "analyticsEnabled", + UNIQUE_TENANT_ID = "uniqueTenantId", + EVENTS = "events", + BACKFILL_METADATA = "backfillMetadata", + EVENTS_RATE_LIMIT = "eventsRateLimit", +} + +export enum TTL { + ONE_MINUTE = 600, + ONE_HOUR = 3600, + ONE_DAY = 86400, +} + +function performExport(funcName: string) { + return (...args: any) => GENERIC[funcName](...args) +} + +export const keys = performExport("keys") +export const get = performExport("get") +export const store = performExport("store") +export const destroy = performExport("delete") +export const withCache = performExport("withCache") +export const bustCache = performExport("bustCache") diff --git a/packages/backend-core/src/cache/index.ts b/packages/backend-core/src/cache/index.ts new file mode 100644 index 0000000000..58928c271a --- /dev/null +++ b/packages/backend-core/src/cache/index.ts @@ -0,0 +1,5 @@ +export * as generic from "./generic" +export * as user from "./user" +export * as app from "./appMetadata" +export * as writethrough from "./writethrough" +export * from "./generic" diff --git a/packages/backend-core/src/cache/tests/writethrough.spec.js b/packages/backend-core/src/cache/tests/writethrough.spec.js index 68db24b325..716d3f9c23 100644 --- a/packages/backend-core/src/cache/tests/writethrough.spec.js +++ b/packages/backend-core/src/cache/tests/writethrough.spec.js @@ -1,6 +1,6 @@ -require("../../../tests/utilities/TestConfiguration") +require("../../../tests") const { Writethrough } = require("../writethrough") -const { dangerousGetDB } = require("../../db") +const { getDB } = require("../../db") const tk = require("timekeeper") const START_DATE = Date.now() @@ -8,8 +8,8 @@ tk.freeze(START_DATE) const DELAY = 5000 -const db = dangerousGetDB("test") -const db2 = dangerousGetDB("test2") +const db = getDB("test") +const db2 = getDB("test2") const writethrough = new Writethrough(db, DELAY), writethrough2 = new Writethrough(db2, DELAY) describe("writethrough", () => { diff --git a/packages/backend-core/src/cache/user.js b/packages/backend-core/src/cache/user.ts similarity index 68% rename from packages/backend-core/src/cache/user.js rename to packages/backend-core/src/cache/user.ts index 130da1915e..a128465cd6 100644 --- a/packages/backend-core/src/cache/user.js +++ b/packages/backend-core/src/cache/user.ts @@ -1,15 +1,16 @@ -const redis = require("../redis/init") -const { getTenantId, lookupTenantId, doWithGlobalDB } = require("../tenancy") -const env = require("../environment") -const accounts = require("../cloud/accounts") +import * as redis from "../redis/init" +import { getTenantId, lookupTenantId, doWithGlobalDB } from "../tenancy" +import env from "../environment" +import * as accounts from "../cloud/accounts" +import { Database } from "@budibase/types" const EXPIRY_SECONDS = 3600 /** * The default populate user function */ -const populateFromDB = async (userId, tenantId) => { - const user = await doWithGlobalDB(tenantId, db => db.get(userId)) +async function populateFromDB(userId: string, tenantId: string) { + const user = await doWithGlobalDB(tenantId, (db: Database) => db.get(userId)) user.budibaseAccess = true if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) { const account = await accounts.getAccount(user.email) @@ -31,7 +32,11 @@ const populateFromDB = async (userId, tenantId) => { * @param {*} populateUser function to provide the user for re-caching. default to couch db * @returns */ -exports.getUser = async (userId, tenantId = null, populateUser = null) => { +export async function getUser( + userId: string, + tenantId?: string, + populateUser?: any +) { if (!populateUser) { populateUser = populateFromDB } @@ -47,7 +52,7 @@ exports.getUser = async (userId, tenantId = null, populateUser = null) => { let user = await client.get(userId) if (!user) { user = await populateUser(userId, tenantId) - client.store(userId, user, EXPIRY_SECONDS) + await client.store(userId, user, EXPIRY_SECONDS) } if (user && !user.tenantId && tenantId) { // make sure the tenant ID is always correct/set @@ -56,7 +61,7 @@ exports.getUser = async (userId, tenantId = null, populateUser = null) => { return user } -exports.invalidateUser = async userId => { +export async function invalidateUser(userId: string) { const client = await redis.getUserClient() await client.delete(userId) } diff --git a/packages/backend-core/src/cache/writethrough.ts b/packages/backend-core/src/cache/writethrough.ts index ec6b1604c8..dc889d5b18 100644 --- a/packages/backend-core/src/cache/writethrough.ts +++ b/packages/backend-core/src/cache/writethrough.ts @@ -1,6 +1,7 @@ import BaseCache from "./base" import { getWritethroughClient } from "../redis/init" import { logWarn } from "../logging" +import { Database } from "@budibase/types" const DEFAULT_WRITE_RATE_MS = 10000 let CACHE: BaseCache | null = null @@ -18,7 +19,7 @@ async function getCache() { return CACHE } -function makeCacheKey(db: PouchDB.Database, key: string) { +function makeCacheKey(db: Database, key: string) { return db.name + key } @@ -27,7 +28,7 @@ function makeCacheItem(doc: any, lastWrite: number | null = null): CacheItem { } export async function put( - db: PouchDB.Database, + db: Database, doc: any, writeRateMs: number = DEFAULT_WRITE_RATE_MS ) { @@ -63,7 +64,7 @@ export async function put( return { ok: true, id: output._id, rev: output._rev } } -export async function get(db: PouchDB.Database, id: string): Promise { +export async function get(db: Database, id: string): Promise { const cache = await getCache() const cacheKey = makeCacheKey(db, id) let cacheItem: CacheItem = await cache.get(cacheKey) @@ -76,7 +77,7 @@ export async function get(db: PouchDB.Database, id: string): Promise { } export async function remove( - db: PouchDB.Database, + db: Database, docOrId: any, rev?: any ): Promise { @@ -94,13 +95,10 @@ export async function remove( } export class Writethrough { - db: PouchDB.Database + db: Database writeRateMs: number - constructor( - db: PouchDB.Database, - writeRateMs: number = DEFAULT_WRITE_RATE_MS - ) { + constructor(db: Database, writeRateMs: number = DEFAULT_WRITE_RATE_MS) { this.db = db this.writeRateMs = writeRateMs } diff --git a/packages/backend-core/src/cloud/accounts.ts b/packages/backend-core/src/cloud/accounts.ts index cca7469060..90fa7ab824 100644 --- a/packages/backend-core/src/cloud/accounts.ts +++ b/packages/backend-core/src/cloud/accounts.ts @@ -1,6 +1,6 @@ import API from "./api" import env from "../environment" -import { Headers } from "../constants" +import { Header } from "../constants" import { CloudAccount } from "@budibase/types" const api = new API(env.ACCOUNT_PORTAL_URL) @@ -14,7 +14,7 @@ export const getAccount = async ( const response = await api.post(`/api/accounts/search`, { body: payload, headers: { - [Headers.API_KEY]: env.ACCOUNT_PORTAL_API_KEY, + [Header.API_KEY]: env.ACCOUNT_PORTAL_API_KEY, }, }) @@ -35,7 +35,7 @@ export const getAccountByTenantId = async ( const response = await api.post(`/api/accounts/search`, { body: payload, headers: { - [Headers.API_KEY]: env.ACCOUNT_PORTAL_API_KEY, + [Header.API_KEY]: env.ACCOUNT_PORTAL_API_KEY, }, }) @@ -50,7 +50,7 @@ export const getAccountByTenantId = async ( export const getStatus = async () => { const response = await api.get(`/api/status`, { headers: { - [Headers.API_KEY]: env.ACCOUNT_PORTAL_API_KEY, + [Header.API_KEY]: env.ACCOUNT_PORTAL_API_KEY, }, }) const json = await response.json() diff --git a/packages/backend-core/src/cloud/api.js b/packages/backend-core/src/cloud/api.js deleted file mode 100644 index d4d4b6c8bb..0000000000 --- a/packages/backend-core/src/cloud/api.js +++ /dev/null @@ -1,42 +0,0 @@ -const fetch = require("node-fetch") -class API { - constructor(host) { - this.host = host - } - - apiCall = - method => - async (url = "", options = {}) => { - if (!options.headers) { - options.headers = {} - } - - if (!options.headers["Content-Type"]) { - options.headers = { - "Content-Type": "application/json", - Accept: "application/json", - ...options.headers, - } - } - - let json = options.headers["Content-Type"] === "application/json" - - const requestOptions = { - method: method, - body: json ? JSON.stringify(options.body) : options.body, - headers: options.headers, - // TODO: See if this is necessary - credentials: "include", - } - - return await fetch(`${this.host}${url}`, requestOptions) - } - - post = this.apiCall("POST") - get = this.apiCall("GET") - patch = this.apiCall("PATCH") - del = this.apiCall("DELETE") - put = this.apiCall("PUT") -} - -module.exports = API diff --git a/packages/backend-core/src/cloud/api.ts b/packages/backend-core/src/cloud/api.ts new file mode 100644 index 0000000000..ef9b9d88e6 --- /dev/null +++ b/packages/backend-core/src/cloud/api.ts @@ -0,0 +1,59 @@ +import fetch from "node-fetch" +import * as logging from "../logging" + +export default class API { + host: string + + constructor(host: string) { + this.host = host + } + + async apiCall(method: string, url: string, options?: any) { + if (!options.headers) { + options.headers = {} + } + + if (!options.headers["Content-Type"]) { + options.headers = { + "Content-Type": "application/json", + Accept: "application/json", + ...options.headers, + } + } + + let json = options.headers["Content-Type"] === "application/json" + + // add x-budibase-correlation-id header + logging.correlation.setHeader(options.headers) + + const requestOptions = { + method: method, + body: json ? JSON.stringify(options.body) : options.body, + headers: options.headers, + // TODO: See if this is necessary + credentials: "include", + } + + return await fetch(`${this.host}${url}`, requestOptions) + } + + async post(url: string, options?: any) { + return this.apiCall("POST", url, options) + } + + async get(url: string, options?: any) { + return this.apiCall("GET", url, options) + } + + async patch(url: string, options?: any) { + return this.apiCall("PATCH", url, options) + } + + async del(url: string, options?: any) { + return this.apiCall("DELETE", url, options) + } + + async put(url: string, options?: any) { + return this.apiCall("PUT", url, options) + } +} diff --git a/packages/backend-core/src/clshooked/index.js b/packages/backend-core/src/clshooked/index.js deleted file mode 100644 index d69ffdd914..0000000000 --- a/packages/backend-core/src/clshooked/index.js +++ /dev/null @@ -1,650 +0,0 @@ -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 deleted file mode 100644 index 44c271a4f8..0000000000 --- a/packages/backend-core/src/constants.js +++ /dev/null @@ -1,44 +0,0 @@ -exports.UserStatus = { - ACTIVE: "active", - INACTIVE: "inactive", -} - -exports.Cookies = { - CurrentApp: "budibase:currentapp", - Auth: "budibase:auth", - Init: "budibase:init", - ACCOUNT_RETURN_URL: "budibase:account:returnurl", - DatasourceAuth: "budibase:datasourceauth", - OIDC_CONFIG: "budibase:oidc:config", -} - -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", -} - -exports.GlobalRoles = { - OWNER: "owner", - ADMIN: "admin", - BUILDER: "builder", - WORKSPACE_MANAGER: "workspace_manager", -} - -exports.Configs = { - SETTINGS: "settings", - ACCOUNT: "account", - SMTP: "smtp", - GOOGLE: "google", - OIDC: "oidc", - OIDC_LOGOS: "logos_oidc", -} - -exports.MAX_VALID_DATE = new Date(2147483647000) -exports.DEFAULT_TENANT_ID = "default" diff --git a/packages/backend-core/src/db/constants.ts b/packages/backend-core/src/constants/db.ts similarity index 73% rename from packages/backend-core/src/db/constants.ts rename to packages/backend-core/src/constants/db.ts index 45ca675fa6..f7d15b3880 100644 --- a/packages/backend-core/src/db/constants.ts +++ b/packages/backend-core/src/constants/db.ts @@ -21,6 +21,7 @@ export enum ViewName { ACCOUNT_BY_EMAIL = "account_by_email", PLATFORM_USERS_LOWERCASE = "platform_users_lowercase", USER_BY_GROUP = "by_group_user", + APP_BACKUP_BY_TRIGGER = "by_trigger", } export const DeprecatedViews = { @@ -30,6 +31,10 @@ export const DeprecatedViews = { ], } +export enum InternalTable { + USER_METADATA = "ta_users", +} + export enum DocumentType { USER = "us", GROUP = "gr", @@ -46,6 +51,23 @@ export enum DocumentType { AUTOMATION_LOG = "log_au", ACCOUNT_METADATA = "acc_metadata", PLUGIN = "plg", + DATASOURCE = "datasource", + DATASOURCE_PLUS = "datasource_plus", + APP_BACKUP = "backup", + TABLE = "ta", + ROW = "ro", + AUTOMATION = "au", + LINK = "li", + WEBHOOK = "wh", + INSTANCE = "inst", + LAYOUT = "layout", + SCREEN = "screen", + QUERY = "query", + DEPLOYMENTS = "deployments", + METADATA = "metadata", + MEM_VIEW = "view", + USER_FLAG = "flag", + AUTOMATION_METADATA = "meta_au", } export const StaticDatabases = { @@ -55,6 +77,7 @@ export const StaticDatabases = { apiKeys: "apikeys", usageQuota: "usage_quota", licenseInfo: "license_info", + environmentVariables: "environmentvariables", }, }, // contains information about tenancy and so on @@ -70,3 +93,4 @@ export const StaticDatabases = { export const APP_PREFIX = DocumentType.APP + SEPARATOR export const APP_DEV = DocumentType.APP_DEV + SEPARATOR export const APP_DEV_PREFIX = APP_DEV +export const BUDIBASE_DATASOURCE_TYPE = "budibase" diff --git a/packages/backend-core/src/constants/index.ts b/packages/backend-core/src/constants/index.ts new file mode 100644 index 0000000000..62d5e08e63 --- /dev/null +++ b/packages/backend-core/src/constants/index.ts @@ -0,0 +1,2 @@ +export * from "./db" +export * from "./misc" diff --git a/packages/backend-core/src/constants/misc.ts b/packages/backend-core/src/constants/misc.ts new file mode 100644 index 0000000000..0bf3df4094 --- /dev/null +++ b/packages/backend-core/src/constants/misc.ts @@ -0,0 +1,45 @@ +export enum UserStatus { + ACTIVE = "active", + INACTIVE = "inactive", +} + +export enum Cookie { + CurrentApp = "budibase:currentapp", + Auth = "budibase:auth", + Init = "budibase:init", + ACCOUNT_RETURN_URL = "budibase:account:returnurl", + DatasourceAuth = "budibase:datasourceauth", + OIDC_CONFIG = "budibase:oidc:config", +} + +export enum Header { + 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", + CORRELATION_ID = "x-budibase-correlation-id", +} + +export enum GlobalRole { + OWNER = "owner", + ADMIN = "admin", + BUILDER = "builder", + WORKSPACE_MANAGER = "workspace_manager", +} + +export enum Config { + SETTINGS = "settings", + ACCOUNT = "account", + SMTP = "smtp", + GOOGLE = "google", + OIDC = "oidc", + OIDC_LOGOS = "logos_oidc", +} + +export const MAX_VALID_DATE = new Date(2147483647000) +export const DEFAULT_TENANT_ID = "default" diff --git a/packages/backend-core/src/context/Context.ts b/packages/backend-core/src/context/Context.ts new file mode 100644 index 0000000000..02b7713764 --- /dev/null +++ b/packages/backend-core/src/context/Context.ts @@ -0,0 +1,14 @@ +import { AsyncLocalStorage } from "async_hooks" +import { ContextMap } from "./mainContext" + +export default class Context { + static storage = new AsyncLocalStorage() + + static run(context: ContextMap, func: any) { + return Context.storage.run(context, () => func()) + } + + static get(): ContextMap { + return Context.storage.getStore() as ContextMap + } +} diff --git a/packages/backend-core/src/context/FunctionContext.js b/packages/backend-core/src/context/FunctionContext.js deleted file mode 100644 index c0ed34fe78..0000000000 --- a/packages/backend-core/src/context/FunctionContext.js +++ /dev/null @@ -1,47 +0,0 @@ -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 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 setOnContext(key, value) { - const namespaceId = MAIN_CTX.get(REQUEST_ID_KEY) - const namespace = cls.getNamespace(namespaceId) - namespace.set(key, value) - } - - static getFromContext(key) { - 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 - } - } -} - -module.exports = FunctionContext diff --git a/packages/backend-core/src/context/constants.ts b/packages/backend-core/src/context/constants.ts deleted file mode 100644 index 937ad8f248..0000000000 --- a/packages/backend-core/src/context/constants.ts +++ /dev/null @@ -1,17 +0,0 @@ -export enum ContextKey { - 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.ts similarity index 58% rename from packages/backend-core/src/context/deprovision.js rename to packages/backend-core/src/context/deprovision.ts index ba3c2d8449..81f03096dc 100644 --- a/packages/backend-core/src/context/deprovision.js +++ b/packages/backend-core/src/context/deprovision.ts @@ -1,15 +1,19 @@ -const { getGlobalUserParams, getAllApps } = require("../db/utils") -const { doWithDB } = require("../db") -const { doWithGlobalDB } = require("../tenancy") -const { StaticDatabases } = require("../db/constants") +import { + getGlobalUserParams, + getAllApps, + doWithDB, + StaticDatabases, +} from "../db" +import { doWithGlobalDB } from "../tenancy" +import { App, Tenants, User, Database } from "@budibase/types" const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name -const removeTenantFromInfoDB = async tenantId => { +async function removeTenantFromInfoDB(tenantId: string) { try { - await doWithDB(PLATFORM_INFO_DB, async infoDb => { - let tenants = await infoDb.get(TENANT_DOC) + await doWithDB(PLATFORM_INFO_DB, async (infoDb: Database) => { + const tenants = (await infoDb.get(TENANT_DOC)) as Tenants tenants.tenantIds = tenants.tenantIds.filter(id => id !== tenantId) await infoDb.put(tenants) @@ -20,14 +24,14 @@ const removeTenantFromInfoDB = async tenantId => { } } -exports.removeUserFromInfoDB = async dbUser => { - await doWithDB(PLATFORM_INFO_DB, async infoDb => { - const keys = [dbUser._id, dbUser.email] +export async function removeUserFromInfoDB(dbUser: User) { + await doWithDB(PLATFORM_INFO_DB, async (infoDb: Database) => { + const keys = [dbUser._id!, dbUser.email] const userDocs = await infoDb.allDocs({ keys, include_docs: true, }) - const toDelete = userDocs.rows.map(row => { + const toDelete = userDocs.rows.map((row: any) => { return { ...row.doc, _deleted: true, @@ -37,18 +41,18 @@ exports.removeUserFromInfoDB = async dbUser => { }) } -const removeUsersFromInfoDB = async tenantId => { - return doWithGlobalDB(tenantId, async db => { +async function removeUsersFromInfoDB(tenantId: string) { + return doWithGlobalDB(tenantId, async (db: any) => { 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) + await doWithDB(PLATFORM_INFO_DB, async (infoDb: any) => { + const allEmails = allUsers.rows.map((row: any) => row.doc.email) // get the id docs - let keys = allUsers.rows.map(row => row.id) + let keys = allUsers.rows.map((row: any) => row.id) // and the email docs keys = keys.concat(allEmails) // retrieve the docs and delete them @@ -56,7 +60,7 @@ const removeUsersFromInfoDB = async tenantId => { keys, include_docs: true, }) - const toDelete = userDocs.rows.map(row => { + const toDelete = userDocs.rows.map((row: any) => { return { ...row.doc, _deleted: true, @@ -71,8 +75,8 @@ const removeUsersFromInfoDB = async tenantId => { }) } -const removeGlobalDB = async tenantId => { - return doWithGlobalDB(tenantId, async db => { +async function removeGlobalDB(tenantId: string) { + return doWithGlobalDB(tenantId, async (db: Database) => { try { await db.destroy() } catch (err) { @@ -82,11 +86,11 @@ const removeGlobalDB = async tenantId => { }) } -const removeTenantApps = async tenantId => { +async function removeTenantApps(tenantId: string) { try { - const apps = await getAllApps({ all: true }) + const apps = (await getAllApps({ all: true })) as App[] const destroyPromises = apps.map(app => - doWithDB(app.appId, db => db.destroy()) + doWithDB(app.appId, (db: Database) => db.destroy()) ) await Promise.allSettled(destroyPromises) } catch (err) { @@ -96,7 +100,7 @@ const removeTenantApps = async tenantId => { } // can't live in tenancy package due to circular dependency on db/utils -exports.deleteTenant = async tenantId => { +export async function deleteTenant(tenantId: string) { await removeTenantFromInfoDB(tenantId) await removeUsersFromInfoDB(tenantId) await removeGlobalDB(tenantId) diff --git a/packages/backend-core/src/context/identity.ts b/packages/backend-core/src/context/identity.ts index 37e1ecf40a..648dd1b5fd 100644 --- a/packages/backend-core/src/context/identity.ts +++ b/packages/backend-core/src/context/identity.ts @@ -2,23 +2,22 @@ import { IdentityContext, IdentityType, User, - UserContext, isCloudAccount, Account, AccountUserContext, } from "@budibase/types" import * as context from "." -export const getIdentity = (): IdentityContext | undefined => { +export function getIdentity(): IdentityContext | undefined { return context.getIdentity() } -export const doInIdentityContext = (identity: IdentityContext, task: any) => { +export function doInIdentityContext(identity: IdentityContext, task: any) { return context.doInIdentityContext(identity, task) } -export const doInUserContext = (user: User, task: any) => { - const userContext: UserContext = { +export function doInUserContext(user: User, task: any) { + const userContext: any = { ...user, _id: user._id as string, type: IdentityType.USER, @@ -26,7 +25,7 @@ export const doInUserContext = (user: User, task: any) => { return doInIdentityContext(userContext, task) } -export const doInAccountContext = (account: Account, task: any) => { +export function doInAccountContext(account: Account, task: any) { const _id = getAccountUserId(account) const tenantId = account.tenantId const accountContext: AccountUserContext = { @@ -38,12 +37,12 @@ export const doInAccountContext = (account: Account, task: any) => { return doInIdentityContext(accountContext, task) } -export const getAccountUserId = (account: Account) => { +export function getAccountUserId(account: Account) { let userId: string if (isCloudAccount(account)) { userId = account.budibaseUserId } else { - // use account id as user id for self hosting + // use account id as user id for self-hosting userId = account.accountId } return userId diff --git a/packages/backend-core/src/context/index.ts b/packages/backend-core/src/context/index.ts index 7cc90e3c67..9c70363170 100644 --- a/packages/backend-core/src/context/index.ts +++ b/packages/backend-core/src/context/index.ts @@ -1,255 +1,3 @@ -import env from "../environment" -import { SEPARATOR, DocumentType } from "../db/constants" -import cls from "./FunctionContext" -import { dangerousGetDB, closeDB } from "../db" -import { baseGlobalDBName } from "../db/tenancy" -import { IdentityContext } from "@budibase/types" -import { DEFAULT_TENANT_ID as _DEFAULT_TENANT_ID } from "../constants" -import { ContextKey } 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(ContextKey.TENANT_ID, null) - cls.setOnContext(ContextKey.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] === DocumentType.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(ContextKey.TENANCY_IN_USE, () => { - return closeTenancy() - }) - } - } - - const existing = cls.getFromContext(ContextKey.TENANT_ID) === tenantId - return updateUsing(ContextKey.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(ContextKey.APP_ID, appId) - - // preserve the identity - if (identity) { - setIdentity(identity) - } - try { - // invoke the task - return await task() - } finally { - await closeWithUsing(ContextKey.APP_IN_USE, async () => { - await closeAppDBs() - await closeTenancy() - }) - } - } - const existing = cls.getFromContext(ContextKey.APP_ID) === appId - return updateUsing(ContextKey.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(ContextKey.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(ContextKey.IDENTITY_IN_USE, async () => { - setIdentity(null) - await closeTenancy() - }) - } - } - - const existing = cls.getFromContext(ContextKey.IDENTITY) - return updateUsing(ContextKey.IDENTITY_IN_USE, existing, internal) -} - -export const getIdentity = (): IdentityContext | undefined => { - try { - return cls.getFromContext(ContextKey.IDENTITY) - } catch (e) { - // do nothing - identity is not in context - } -} - -export const updateTenantId = (tenantId: string | null) => { - cls.setOnContext(ContextKey.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(ContextKey.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(ContextKey.GLOBAL_DB, db) - return db -} - -export const getGlobalDB = () => { - const db = cls.getFromContext(ContextKey.GLOBAL_DB) - if (!db) { - throw new Error("Global DB not found") - } - return db -} - -export const isTenantIdSet = () => { - const tenantId = cls.getFromContext(ContextKey.TENANT_ID) - return !!tenantId -} - -export const getTenantId = () => { - if (!isMultiTenant()) { - return DEFAULT_TENANT_ID - } - const tenantId = cls.getFromContext(ContextKey.TENANT_ID) - if (!tenantId) { - throw new Error("Tenant id not found") - } - return tenantId -} - -export const getAppId = () => { - const foundId = cls.getFromContext(ContextKey.APP_ID) - if (!foundId && env.isTest() && TEST_APP_ID) { - return TEST_APP_ID - } else { - return foundId - } -} - -export const isTenancyEnabled = () => { - return env.MULTI_TENANCY -} - -/** - * Opens the app database based on whatever the request - * contained, dev or prod. - */ -export const getAppDB = (opts?: any) => { - return getContextDB(ContextKey.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(ContextKey.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(ContextKey.DEV_DB, opts) -} +export { DEFAULT_TENANT_ID } from "../constants" +export * as identity from "./identity" +export * from "./mainContext" diff --git a/packages/backend-core/src/context/mainContext.ts b/packages/backend-core/src/context/mainContext.ts new file mode 100644 index 0000000000..9884d25d5a --- /dev/null +++ b/packages/backend-core/src/context/mainContext.ts @@ -0,0 +1,262 @@ +// some test cases call functions directly, need to +// store an app ID to pretend there is a context +import env from "../environment" +import Context from "./Context" +import * as conversions from "../db/conversions" +import { getDB } from "../db/db" +import { + DocumentType, + SEPARATOR, + StaticDatabases, + DEFAULT_TENANT_ID, +} from "../constants" +import { Database, IdentityContext } from "@budibase/types" + +export type ContextMap = { + tenantId?: string + appId?: string + identity?: IdentityContext + environmentVariables?: Record +} + +let TEST_APP_ID: string | null = null + +export function 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 function baseGlobalDBName(tenantId: string | undefined | null) { + let dbName + if (!tenantId || tenantId === DEFAULT_TENANT_ID) { + dbName = StaticDatabases.GLOBAL.name + } else { + dbName = `${tenantId}${SEPARATOR}${StaticDatabases.GLOBAL.name}` + } + return dbName +} + +export function isMultiTenant() { + return env.MULTI_TENANCY +} + +export function isTenantIdSet() { + const context = Context.get() + return !!context?.tenantId +} + +export function isTenancyEnabled() { + 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 function getTenantIDFromAppID(appId: string) { + if (!appId) { + return undefined + } + if (!isMultiTenant()) { + return DEFAULT_TENANT_ID + } + const split = appId.split(SEPARATOR) + const hasDev = split[1] === DocumentType.DEV + if ((hasDev && split.length === 3) || (!hasDev && split.length === 2)) { + return undefined + } + if (hasDev) { + return split[2] + } else { + return split[1] + } +} + +function updateContext(updates: ContextMap): ContextMap { + let context: ContextMap + try { + context = Context.get() + } catch (err) { + // no context, start empty + context = {} + } + context = { + ...context, + ...updates, + } + return context +} + +async function newContext(updates: ContextMap, task: any) { + // see if there already is a context setup + let context: ContextMap = updateContext(updates) + return Context.run(context, task) +} + +export async function doInContext(appId: string, task: any): Promise { + const tenantId = getTenantIDFromAppID(appId) + return newContext( + { + tenantId, + appId, + }, + task + ) +} + +export async function doInTenant( + tenantId: string | null, + task: any +): Promise { + // make sure default always selected in single tenancy + if (!env.MULTI_TENANCY) { + tenantId = tenantId || DEFAULT_TENANT_ID + } + + const updates = tenantId ? { tenantId } : {} + return newContext(updates, task) +} + +export async function doInAppContext( + appId: string | null, + task: any +): Promise { + if (!appId && !env.isTest()) { + throw new Error("appId is required") + } + + let updates: ContextMap + if (!appId) { + updates = { appId: "" } + } else { + const tenantId = getTenantIDFromAppID(appId) + updates = { appId } + if (tenantId) { + updates.tenantId = tenantId + } + } + return newContext(updates, task) +} + +export async function doInIdentityContext( + identity: IdentityContext, + task: any +): Promise { + if (!identity) { + throw new Error("identity is required") + } + + const context: ContextMap = { + identity, + } + if (identity.tenantId) { + context.tenantId = identity.tenantId + } + return newContext(context, task) +} + +export function getIdentity(): IdentityContext | undefined { + try { + const context = Context.get() + return context?.identity + } catch (e) { + // do nothing - identity is not in context + } +} + +export function getTenantId(): string { + if (!isMultiTenant()) { + return DEFAULT_TENANT_ID + } + const context = Context.get() + const tenantId = context?.tenantId + if (!tenantId) { + throw new Error("Tenant id not found") + } + return tenantId +} + +export function getAppId(): string | undefined { + const context = Context.get() + const foundId = context?.appId + if (!foundId && env.isTest() && TEST_APP_ID) { + return TEST_APP_ID + } else { + return foundId + } +} + +export const getProdAppId = () => { + const appId = getAppId() + if (!appId) { + throw new Error("Could not get appId") + } + return conversions.getProdAppID(appId) +} + +export function doInEnvironmentContext( + values: Record, + task: any +) { + if (!values) { + throw new Error("Must supply environment variables.") + } + const updates = { + environmentVariables: values, + } + return newContext(updates, task) +} + +export function getEnvironmentVariables() { + const context = Context.get() + if (!context.environmentVariables) { + return null + } else { + return context.environmentVariables + } +} + +export function getGlobalDB(): Database { + const context = Context.get() + if (!context || (env.MULTI_TENANCY && !context.tenantId)) { + throw new Error("Global DB not found") + } + return getDB(baseGlobalDBName(context?.tenantId)) +} + +/** + * Gets the app database based on whatever the request + * contained, dev or prod. + */ +export function getAppDB(opts?: any): Database { + const appId = getAppId() + return getDB(appId, opts) +} + +/** + * This specifically gets the prod app ID, if the request + * contained a development app ID, this will get the prod one. + */ +export function getProdAppDB(opts?: any): Database { + const appId = getAppId() + if (!appId) { + throw new Error("Unable to retrieve prod DB - no app ID.") + } + return getDB(conversions.getProdAppID(appId), opts) +} + +/** + * This specifically gets the dev app ID, if the request + * contained a prod app ID, this will get the dev one. + */ +export function getDevAppDB(opts?: any): Database { + const appId = getAppId() + if (!appId) { + throw new Error("Unable to retrieve dev DB - no app ID.") + } + return getDB(conversions.getDevelopmentAppID(appId), opts) +} diff --git a/packages/backend-core/src/context/tests/index.spec.ts b/packages/backend-core/src/context/tests/index.spec.ts index 55ecd333a3..c9b5870ffa 100644 --- a/packages/backend-core/src/context/tests/index.spec.ts +++ b/packages/backend-core/src/context/tests/index.spec.ts @@ -1,18 +1,9 @@ -import "../../../tests/utilities/TestConfiguration" -import * as context from ".." -import { DEFAULT_TENANT_ID } from "../../constants" +require("../../../tests") +const context = require("../") +const { DEFAULT_TENANT_ID } = require("../../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", () => { @@ -25,8 +16,6 @@ describe("context", () => { const db = context.getGlobalDB() expect(db.name).toBe("global-db") }) - expect(dbUtils.dangerousGetDB).toHaveBeenCalledTimes(1) - expect(dbUtils.closeDB).toHaveBeenCalledTimes(1) }) }) @@ -37,10 +26,10 @@ describe("context", () => { it("fails when no tenant id is set", () => { const test = () => { - let error + let error: any try { context.getTenantId() - } catch (e: any) { + } catch (e) { error = e } expect(error.message).toBe("Tenant id not found") @@ -56,10 +45,10 @@ describe("context", () => { it("fails when no tenant db is set", () => { const test = () => { - let error + let error: any try { context.getGlobalDB() - } catch (e: any) { + } catch (e) { error = e } expect(error.message).toBe("Global DB not found") @@ -85,8 +74,6 @@ describe("context", () => { 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 () => { @@ -121,10 +108,6 @@ describe("context", () => { }) }) }) - - // 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", () => { diff --git a/packages/backend-core/src/context/utils.ts b/packages/backend-core/src/context/utils.ts deleted file mode 100644 index 6e7100b594..0000000000 --- a/packages/backend-core/src/context/utils.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { - DEFAULT_TENANT_ID, - getAppId, - getTenantIDFromAppID, - updateTenantId, -} from "./index" -import cls from "./FunctionContext" -import { IdentityContext } from "@budibase/types" -import { ContextKey } 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(ContextKey.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 = [ContextKey.CURRENT_DB, ContextKey.PROD_DB, ContextKey.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(ContextKey.APP_ID)) { - cls.setOnContext(ContextKey.APP_ID, null) - } - if (cls.getFromContext(ContextKey.DB_OPTS)) { - cls.setOnContext(ContextKey.DB_OPTS, null) - } -} - -export function getContextDB(key: string, opts: any) { - const dbOptsKey = `${key}${ContextKey.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 ContextKey.CURRENT_DB: - toUseAppId = appId - break - case ContextKey.PROD_DB: - toUseAppId = getProdAppID(appId) - break - case ContextKey.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.ts b/packages/backend-core/src/db/Replication.ts index e0bd3c7a43..eb9d613a58 100644 --- a/packages/backend-core/src/db/Replication.ts +++ b/packages/backend-core/src/db/Replication.ts @@ -1,5 +1,5 @@ -import { dangerousGetDB, closeDB } from "." -import { DocumentType } from "./constants" +import { getPouchDB, closePouchDB } from "./couch" +import { DocumentType } from "../constants" class Replication { source: any @@ -12,12 +12,12 @@ class Replication { * @param {String} target - the DB you want to replicate to, or rollback from */ constructor({ source, target }: any) { - this.source = dangerousGetDB(source) - this.target = dangerousGetDB(target) + this.source = getPouchDB(source) + this.target = getPouchDB(target) } close() { - return Promise.all([closeDB(this.source), closeDB(this.target)]) + return Promise.all([closePouchDB(this.source), closePouchDB(this.target)]) } promisify(operation: any, opts = {}) { @@ -68,7 +68,7 @@ class Replication { async rollback() { await this.target.destroy() // Recreate the DB again - this.target = dangerousGetDB(this.target.name) + this.target = getPouchDB(this.target.name) // take the opportunity to remove deleted tombstones await this.replicate() } diff --git a/packages/backend-core/src/db/conversions.js b/packages/backend-core/src/db/conversions.ts similarity index 68% rename from packages/backend-core/src/db/conversions.js rename to packages/backend-core/src/db/conversions.ts index 5b1a785ecc..381c5cb90f 100644 --- a/packages/backend-core/src/db/conversions.js +++ b/packages/backend-core/src/db/conversions.ts @@ -1,32 +1,33 @@ +import { APP_DEV_PREFIX, APP_PREFIX } from "../constants" +import { App } from "@budibase/types" const NO_APP_ERROR = "No app provided" -const { APP_DEV_PREFIX, APP_PREFIX } = require("./constants") -exports.isDevAppID = appId => { +export function isDevAppID(appId?: string) { if (!appId) { throw NO_APP_ERROR } return appId.startsWith(APP_DEV_PREFIX) } -exports.isProdAppID = appId => { +export function isProdAppID(appId?: string) { if (!appId) { throw NO_APP_ERROR } - return appId.startsWith(APP_PREFIX) && !exports.isDevAppID(appId) + return appId.startsWith(APP_PREFIX) && !isDevAppID(appId) } -exports.isDevApp = app => { +export function isDevApp(app: App) { if (!app) { throw NO_APP_ERROR } - return exports.isDevAppID(app.appId) + return isDevAppID(app.appId) } /** * Generates a development app ID from a real app ID. * @returns {string} the dev app ID which can be used for dev database. */ -exports.getDevelopmentAppID = appId => { +export function getDevelopmentAppID(appId: string) { if (!appId || appId.startsWith(APP_DEV_PREFIX)) { return appId } @@ -36,12 +37,12 @@ exports.getDevelopmentAppID = appId => { const rest = split.join(APP_PREFIX) return `${APP_DEV_PREFIX}${rest}` } -exports.getDevAppID = exports.getDevelopmentAppID +export const getDevAppID = getDevelopmentAppID /** * Convert a development app ID to a deployed app ID. */ -exports.getProdAppID = appId => { +export function getProdAppID(appId: string) { if (!appId || !appId.startsWith(APP_DEV_PREFIX)) { return appId } @@ -52,7 +53,7 @@ exports.getProdAppID = appId => { return `${APP_PREFIX}${rest}` } -exports.extractAppUUID = id => { +export function extractAppUUID(id: string) { const split = id?.split("_") || [] return split.length ? split[split.length - 1] : null } diff --git a/packages/backend-core/src/db/couch/DatabaseImpl.ts b/packages/backend-core/src/db/couch/DatabaseImpl.ts new file mode 100644 index 0000000000..a60a748bdc --- /dev/null +++ b/packages/backend-core/src/db/couch/DatabaseImpl.ts @@ -0,0 +1,225 @@ +import Nano from "@budibase/nano" +import { + AllDocsResponse, + AnyDocument, + Database, + DatabaseOpts, + DatabaseQueryOpts, + DatabasePutOpts, + DatabaseCreateIndexOpts, + DatabaseDeleteIndexOpts, + Document, + isDocument, +} from "@budibase/types" +import { getCouchInfo } from "./connections" +import { directCouchCall } from "./utils" +import { getPouchDB } from "./pouchDB" +import { WriteStream, ReadStream } from "fs" +import { newid } from "../../newid" + +function buildNano(couchInfo: { url: string; cookie: string }) { + return Nano({ + url: couchInfo.url, + requestDefaults: { + headers: { + Authorization: couchInfo.cookie, + }, + }, + parseUrl: false, + }) +} + +export function DatabaseWithConnection( + dbName: string, + connection: string, + opts?: DatabaseOpts +) { + if (!connection) { + throw new Error("Must provide connection details") + } + return new DatabaseImpl(dbName, opts, connection) +} + +export class DatabaseImpl implements Database { + public readonly name: string + private static nano: Nano.ServerScope + private readonly instanceNano?: Nano.ServerScope + private readonly pouchOpts: DatabaseOpts + + constructor(dbName?: string, opts?: DatabaseOpts, connection?: string) { + if (dbName == null) { + throw new Error("Database name cannot be undefined.") + } + this.name = dbName + this.pouchOpts = opts || {} + if (connection) { + const couchInfo = getCouchInfo(connection) + this.instanceNano = buildNano(couchInfo) + } + if (!DatabaseImpl.nano) { + DatabaseImpl.init() + } + } + + static init() { + const couchInfo = getCouchInfo() + DatabaseImpl.nano = buildNano(couchInfo) + } + + async exists() { + let response = await directCouchCall(`/${this.name}`, "HEAD") + return response.status === 200 + } + + private nano() { + return this.instanceNano || DatabaseImpl.nano + } + + async checkSetup() { + let shouldCreate = !this.pouchOpts?.skip_setup + // check exists in a lightweight fashion + let exists = await this.exists() + if (!shouldCreate && !exists) { + throw new Error("DB does not exist") + } + if (!exists) { + await this.nano().db.create(this.name) + } + return this.nano().db.use(this.name) + } + + private async updateOutput(fnc: any) { + try { + return await fnc() + } catch (err: any) { + if (err.statusCode) { + err.status = err.statusCode + } + throw err + } + } + + async get(id?: string): Promise { + const db = await this.checkSetup() + if (!id) { + throw new Error("Unable to get doc without a valid _id.") + } + return this.updateOutput(() => db.get(id)) + } + + async remove(idOrDoc: string | Document, rev?: string) { + const db = await this.checkSetup() + let _id: string + let _rev: string + + if (isDocument(idOrDoc)) { + _id = idOrDoc._id! + _rev = idOrDoc._rev! + } else { + _id = idOrDoc + _rev = rev! + } + + if (!_id || !_rev) { + throw new Error("Unable to remove doc without a valid _id and _rev.") + } + return this.updateOutput(() => db.destroy(_id, _rev)) + } + + async post(document: AnyDocument, opts?: DatabasePutOpts) { + if (!document._id) { + document._id = newid() + } + return this.put(document, opts) + } + + async put(document: AnyDocument, opts?: DatabasePutOpts) { + if (!document._id) { + throw new Error("Cannot store document without _id field.") + } + const db = await this.checkSetup() + if (!document.createdAt) { + document.createdAt = new Date().toISOString() + } + document.updatedAt = new Date().toISOString() + if (opts?.force && document._id) { + try { + const existing = await this.get(document._id) + if (existing) { + document._rev = existing._rev + } + } catch (err: any) { + if (err.status !== 404) { + throw err + } + } + } + return this.updateOutput(() => db.insert(document)) + } + + async bulkDocs(documents: AnyDocument[]) { + const db = await this.checkSetup() + return this.updateOutput(() => db.bulk({ docs: documents })) + } + + async allDocs(params: DatabaseQueryOpts): Promise> { + const db = await this.checkSetup() + return this.updateOutput(() => db.list(params)) + } + + async query( + viewName: string, + params: DatabaseQueryOpts + ): Promise> { + const db = await this.checkSetup() + const [database, view] = viewName.split("/") + return this.updateOutput(() => db.view(database, view, params)) + } + + async destroy() { + try { + await this.nano().db.destroy(this.name) + } catch (err: any) { + // didn't exist, don't worry + if (err.statusCode === 404) { + return + } else { + throw { ...err, status: err.statusCode } + } + } + } + + async compact() { + const db = await this.checkSetup() + return this.updateOutput(() => db.compact()) + } + + // All below functions are in-frequently called, just utilise PouchDB + // for them as it implements them better than we can + async dump(stream: WriteStream, opts?: { filter?: any }) { + const pouch = getPouchDB(this.name) + // @ts-ignore + return pouch.dump(stream, opts) + } + + async load(stream: ReadStream) { + const pouch = getPouchDB(this.name) + // @ts-ignore + return pouch.load(stream) + } + + async createIndex(opts: DatabaseCreateIndexOpts) { + const pouch = getPouchDB(this.name) + return pouch.createIndex(opts) + } + + async deleteIndex(opts: DatabaseDeleteIndexOpts) { + const pouch = getPouchDB(this.name) + return pouch.deleteIndex(opts) + } + + async getIndexes() { + const pouch = getPouchDB(this.name) + return pouch.getIndexes() + } +} diff --git a/packages/backend-core/src/db/pouch.js b/packages/backend-core/src/db/couch/connections.ts similarity index 55% rename from packages/backend-core/src/db/pouch.js rename to packages/backend-core/src/db/couch/connections.ts index 12d7d787e3..06c661f350 100644 --- a/packages/backend-core/src/db/pouch.js +++ b/packages/backend-core/src/db/couch/connections.ts @@ -1,7 +1,39 @@ -const PouchDB = require("pouchdb") -const env = require("../environment") +import env from "../../environment" -exports.getUrlInfo = (url = env.COUCH_DB_URL) => { +export const getCouchInfo = (connection?: string) => { + const urlInfo = getUrlInfo(connection) + 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}`, + } +} + +export const getUrlInfo = (url = env.COUCH_DB_URL) => { let cleanUrl, username, password, host if (url) { // Ensure the URL starts with a protocol @@ -43,82 +75,3 @@ exports.getUrlInfo = (url = env.COUCH_DB_URL) => { }, } } - -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.onDisk) { - POUCH_DB_DEFAULTS = { - prefix: undefined, - adapter: "leveldb", - } - } - - 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/couch/index.ts b/packages/backend-core/src/db/couch/index.ts new file mode 100644 index 0000000000..c731d20d6c --- /dev/null +++ b/packages/backend-core/src/db/couch/index.ts @@ -0,0 +1,4 @@ +export * from "./connections" +export * from "./DatabaseImpl" +export * from "./utils" +export { init, getPouch, getPouchDB, closePouchDB } from "./pouchDB" diff --git a/packages/backend-core/src/db/couch/pouchDB.ts b/packages/backend-core/src/db/couch/pouchDB.ts new file mode 100644 index 0000000000..a6f4323d88 --- /dev/null +++ b/packages/backend-core/src/db/couch/pouchDB.ts @@ -0,0 +1,97 @@ +import PouchDB from "pouchdb" +import env from "../../environment" +import { PouchOptions } from "@budibase/types" +import { getCouchInfo } from "./connections" + +let Pouch: any +let initialised = false + +/** + * 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. + */ +export const getPouch = (opts: PouchOptions = {}) => { + let { url, cookie } = getCouchInfo() + let POUCH_DB_DEFAULTS = { + prefix: url, + fetch: (url: string, opts: any) => { + // 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 = { + // @ts-ignore + adapter: "memory", + } + } + + if (opts.onDisk) { + POUCH_DB_DEFAULTS = { + // @ts-ignore + adapter: "leveldb", + } + } + + if (opts.replication) { + const replicationStream = require("pouchdb-replication-stream") + PouchDB.plugin(replicationStream.plugin) + // @ts-ignore + PouchDB.adapter("writableStream", replicationStream.adapters.writableStream) + } + + if (opts.find) { + const find = require("pouchdb-find") + PouchDB.plugin(find) + } + + return PouchDB.defaults(POUCH_DB_DEFAULTS) +} + +export function init(opts?: PouchOptions) { + Pouch = getPouch(opts) + initialised = true +} + +const checkInitialised = () => { + if (!initialised) { + throw new Error("init has not been called") + } +} + +export function getPouchDB(dbName: string, opts?: any): PouchDB.Database { + checkInitialised() + const db = new Pouch(dbName, opts) + const dbPut = db.put + db.put = async (doc: any, options = {}) => { + if (!doc.createdAt) { + doc.createdAt = new Date().toISOString() + } + doc.updatedAt = new Date().toISOString() + return dbPut(doc, options) + } + db.exists = async () => { + const info = await db.info() + return !info.error + } + return db +} + +// use this function if you have called getPouchDB - close +// the databases you've opened once finished +export async function closePouchDB(db: PouchDB.Database) { + if (!db || env.isTest()) { + return + } + try { + // specifically await so that if there is an error, it can be ignored + return await db.close() + } catch (err) { + // ignore error, already closed + } +} diff --git a/packages/backend-core/src/db/couch/utils.ts b/packages/backend-core/src/db/couch/utils.ts new file mode 100644 index 0000000000..426bf92158 --- /dev/null +++ b/packages/backend-core/src/db/couch/utils.ts @@ -0,0 +1,36 @@ +import { getCouchInfo } from "./connections" +import fetch from "node-fetch" +import { checkSlashesInUrl } from "../../helpers" + +export async function directCouchCall( + path: string, + method: string = "GET", + body?: any +) { + let { url, cookie } = getCouchInfo() + const couchUrl = `${url}/${path}` + const params: any = { + method: method, + headers: { + Authorization: cookie, + }, + } + if (body && method !== "GET") { + params.body = JSON.stringify(body) + params.headers["Content-Type"] = "application/json" + } + return await fetch(checkSlashesInUrl(encodeURI(couchUrl)), params) +} + +export async function directCouchQuery( + path: string, + method: string = "GET", + body?: any +) { + const response = await directCouchCall(path, method, body) + if (response.status < 300) { + return await response.json() + } else { + throw "Cannot connect to CouchDB instance" + } +} diff --git a/packages/backend-core/src/db/db.ts b/packages/backend-core/src/db/db.ts new file mode 100644 index 0000000000..3887e8b09f --- /dev/null +++ b/packages/backend-core/src/db/db.ts @@ -0,0 +1,46 @@ +import env from "../environment" +import { directCouchQuery, getPouchDB } from "./couch" +import { CouchFindOptions, Database } from "@budibase/types" +import { DatabaseImpl } from "../db" + +const dbList = new Set() + +export function getDB(dbName?: string, opts?: any): Database { + // TODO: once using the test image, need to remove this + if (env.isTest()) { + dbList.add(dbName) + // @ts-ignore + return getPouchDB(dbName, opts) + } + return new DatabaseImpl(dbName, opts) +} + +// 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 +export async function doWithDB(dbName: string, cb: any, opts = {}) { + const db = getDB(dbName, opts) + // need this to be async so that we can correctly close DB after all + // async operations have been completed + return await cb(db) +} + +export function allDbs() { + if (!env.isTest()) { + throw new Error("Cannot be used outside test environment.") + } + return [...dbList] +} + +export async function directCouchAllDbs(queryString?: string) { + let couchPath = "/_all_dbs" + if (queryString) { + couchPath += `?${queryString}` + } + return await directCouchQuery(couchPath) +} + +export async function directCouchFind(dbName: string, opts: CouchFindOptions) { + const json = await directCouchQuery(`${dbName}/_find`, "POST", opts) + return { rows: json.docs, bookmark: json.bookmark } +} diff --git a/packages/backend-core/src/db/index.js b/packages/backend-core/src/db/index.js deleted file mode 100644 index aa6f7ebc2c..0000000000 --- a/packages/backend-core/src/db/index.js +++ /dev/null @@ -1,91 +0,0 @@ -const pouch = require("./pouch") -const env = require("../environment") - -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) -} - -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") - } -} - -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/index.ts b/packages/backend-core/src/db/index.ts new file mode 100644 index 0000000000..0d9f75fa18 --- /dev/null +++ b/packages/backend-core/src/db/index.ts @@ -0,0 +1,9 @@ +export * from "./couch" +export * from "./db" +export * from "./utils" +export * from "./views" +export * from "./conversions" +export { default as Replication } from "./Replication" +// exports to support old export structure +export * from "../constants/db" +export { getGlobalDBName, baseGlobalDBName } from "../context" diff --git a/packages/backend-core/src/db/tenancy.ts b/packages/backend-core/src/db/tenancy.ts deleted file mode 100644 index d920f7cd41..0000000000 --- a/packages/backend-core/src/db/tenancy.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { DEFAULT_TENANT_ID } from "../constants" -import { StaticDatabases, SEPARATOR } from "./constants" -import { getTenantId } from "../context" - -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 baseGlobalDBName = (tenantId: string | undefined | null) => { - 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/db/tests/index.spec.js b/packages/backend-core/src/db/tests/index.spec.js index bc0c638126..fc0094d354 100644 --- a/packages/backend-core/src/db/tests/index.spec.js +++ b/packages/backend-core/src/db/tests/index.spec.js @@ -1,11 +1,11 @@ -require("../../../tests/utilities/TestConfiguration") -const { dangerousGetDB } = require("../") +require("../../../tests") +const { getDB } = require("../") describe("db", () => { describe("getDB", () => { it("returns a db", async () => { - const db = dangerousGetDB("test") + const db = getDB("test") expect(db).toBeDefined() expect(db._adapter).toBe("memory") expect(db.prefix).toBe("_pouch_") @@ -13,7 +13,7 @@ describe("db", () => { }) it("uses the custom put function", async () => { - const db = dangerousGetDB("test") + const db = getDB("test") let doc = { _id: "test" } await db.put(doc) doc = await db.get(doc._id) diff --git a/packages/backend-core/src/db/tests/pouch.spec.js b/packages/backend-core/src/db/tests/pouch.spec.js index 30cdd0f5ec..f0abc82240 100644 --- a/packages/backend-core/src/db/tests/pouch.spec.js +++ b/packages/backend-core/src/db/tests/pouch.spec.js @@ -1,5 +1,5 @@ -require("../../../tests/utilities/TestConfiguration") -const getUrlInfo = require("../pouch").getUrlInfo +require("../../../tests") +const getUrlInfo = require("../couch").getUrlInfo describe("pouch", () => { describe("Couch DB URL parsing", () => { diff --git a/packages/backend-core/src/db/tests/utils.spec.js b/packages/backend-core/src/db/tests/utils.spec.ts similarity index 90% rename from packages/backend-core/src/db/tests/utils.spec.js rename to packages/backend-core/src/db/tests/utils.spec.ts index 5f9a224e7a..37b7ce51e2 100644 --- a/packages/backend-core/src/db/tests/utils.spec.js +++ b/packages/backend-core/src/db/tests/utils.spec.ts @@ -1,20 +1,17 @@ -require("../../../tests/utilities/TestConfiguration"); +require("../../../tests") 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") +} = require("../conversions") +const { generateAppID, getPlatformUrl, getScopedConfig } = require("../utils") +const tenancy = require("../../tenancy") +const { Config, DEFAULT_TENANT_ID } = require("../../constants") +import env from "../../environment" describe("utils", () => { describe("app ID manipulation", () => { - function getID() { const appId = generateAppID() const split = appId.split("_") @@ -26,42 +23,42 @@ describe("utils", () => { 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) @@ -77,10 +74,10 @@ const setDbPlatformUrl = async () => { const db = tenancy.getGlobalDB() db.put({ _id: "config_settings", - type: Configs.SETTINGS, + type: Config.SETTINGS, config: { - platformUrl: DB_URL - } + platformUrl: DB_URL, + }, }) } @@ -90,17 +87,16 @@ const clearSettingsConfig = async () => { try { const config = await db.get("config_settings") await db.remove("config_settings", config._rev) - } catch (e) { + } catch (e: any) { if (e.status !== 404) { throw e } } }) } - + describe("getPlatformUrl", () => { describe("self host", () => { - beforeEach(async () => { env._set("SELF_HOST", 1) await clearSettingsConfig() @@ -127,10 +123,9 @@ describe("getPlatformUrl", () => { const url = await getPlatformUrl() expect(url).toBe(DB_URL) }) - }) + }) }) - describe("cloud", () => { const TENANT_AWARE_URL = "http://default.env.com" @@ -161,13 +156,12 @@ describe("getPlatformUrl", () => { 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", "") @@ -178,7 +172,7 @@ describe("getScopedConfig", () => { await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => { await setDbPlatformUrl() const db = tenancy.getGlobalDB() - const config = await getScopedConfig(db, { type: Configs.SETTINGS }) + const config = await getScopedConfig(db, { type: Config.SETTINGS }) expect(config.platformUrl).toBe(DB_URL) }) }) @@ -186,7 +180,7 @@ describe("getScopedConfig", () => { 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 }) + const config = await getScopedConfig(db, { type: Config.SETTINGS }) expect(config.platformUrl).toBe(DEFAULT_URL) }) }) diff --git a/packages/backend-core/src/db/utils.ts b/packages/backend-core/src/db/utils.ts index a12c6bed4f..5e501c8d22 100644 --- a/packages/backend-core/src/db/utils.ts +++ b/packages/backend-core/src/db/utils.ts @@ -1,28 +1,26 @@ -import { newid } from "../hashing" -import { DEFAULT_TENANT_ID, Configs } from "../constants" +import { newid } from "../newid" import env from "../environment" -import { SEPARATOR, DocumentType, UNICODE_MAX, ViewName } from "./constants" -import { getTenantId, getGlobalDB } from "../context" -import { getGlobalDBName } from "./tenancy" -import fetch from "node-fetch" -import { doWithDB, allDbs } from "./index" -import { getCouchInfo } from "./pouch" +import { + DEFAULT_TENANT_ID, + SEPARATOR, + DocumentType, + UNICODE_MAX, + ViewName, + InternalTable, + APP_PREFIX, +} from "../constants" +import { getTenantId, getGlobalDB, getGlobalDBName } from "../context" +import { doWithDB, allDbs, directCouchAllDbs } from "./db" import { getAppMetadata } from "../cache/appMetadata" -import { checkSlashesInUrl } from "../helpers" import { isDevApp, isDevAppID, getProdAppID } from "./conversions" -import { APP_PREFIX } from "./constants" import * as events from "../events" - -export * from "./constants" -export * from "./conversions" -export { default as Replication } from "./Replication" -export * from "./tenancy" +import { App, Database, ConfigType, isSettingsConfig } from "@budibase/types" /** * Generates a new app ID. * @returns {string} The new app ID which the app doc can be stored under. */ -export const generateAppID = (tenantId = null) => { +export const generateAppID = (tenantId?: string | null) => { let id = APP_PREFIX if (tenantId) { id += `${tenantId}${SEPARATOR}` @@ -43,8 +41,8 @@ export const generateAppID = (tenantId = null) => { * @returns {object} Parameters which can then be used with an allDocs request. */ export function getDocParams( - docType: any, - docId: any = null, + docType: string, + docId?: string | null, otherProps: any = {} ) { if (docId == null) { @@ -57,6 +55,28 @@ export function getDocParams( } } +/** + * Gets the DB allDocs/query params for retrieving a row. + * @param {string|null} tableId The table in which the rows have been stored. + * @param {string|null} rowId The ID of the row which is being specifically queried for. This can be + * left null to get all the rows in the table. + * @param {object} otherProps Any other properties to add to the request. + * @returns {object} Parameters which can then be used with an allDocs request. + */ +export function getRowParams( + tableId?: string | null, + rowId?: string | null, + otherProps = {} +) { + if (tableId == null) { + return getDocParams(DocumentType.ROW, null, otherProps) + } + + const endOfKey = rowId == null ? `${tableId}${SEPARATOR}` : rowId + + return getDocParams(DocumentType.ROW, endOfKey, otherProps) +} + /** * Retrieve the correct index for a view based on default design DB. */ @@ -64,6 +84,39 @@ export function getQueryIndex(viewName: ViewName) { return `database/${viewName}` } +/** + * Gets a new row ID for the specified table. + * @param {string} tableId The table which the row is being created for. + * @param {string|null} id If an ID is to be used then the UUID can be substituted for this. + * @returns {string} The new ID which a row doc can be stored under. + */ +export function generateRowID(tableId: string, id?: string) { + id = id || newid() + return `${DocumentType.ROW}${SEPARATOR}${tableId}${SEPARATOR}${id}` +} + +/** + * Check if a given ID is that of a table. + * @returns {boolean} + */ +export const isTableId = (id: string) => { + // this includes datasource plus tables + return ( + id && + (id.startsWith(`${DocumentType.TABLE}${SEPARATOR}`) || + id.startsWith(`${DocumentType.DATASOURCE_PLUS}${SEPARATOR}`)) + ) +} + +/** + * Check if a given ID is that of a datasource or datasource plus. + * @returns {boolean} + */ +export const isDatasourceId = (id: string) => { + // this covers both datasources and datasource plus + return id && id.startsWith(`${DocumentType.DATASOURCE}${SEPARATOR}`) +} + /** * Generates a new workspace ID. * @returns {string} The new workspace ID which the workspace doc can be stored under. @@ -109,6 +162,33 @@ export function getGlobalUserParams(globalId: any, otherProps: any = {}) { } } +/** + * Gets parameters for retrieving users, this is a utility function for the getDocParams function. + */ +export function getUserMetadataParams(userId?: string | null, otherProps = {}) { + return getRowParams(InternalTable.USER_METADATA, userId, otherProps) +} + +/** + * Generates a new user ID based on the passed in global ID. + * @param {string} globalId The ID of the global user. + * @returns {string} The new user ID which the user doc can be stored under. + */ +export function generateUserMetadataID(globalId: string) { + return generateRowID(InternalTable.USER_METADATA, globalId) +} + +/** + * Breaks up the ID to get the global ID. + */ +export function getGlobalIDFromUserMetadataID(id: string) { + const prefix = `${DocumentType.ROW}${SEPARATOR}${InternalTable.USER_METADATA}${SEPARATOR}` + if (!id || !id.includes(prefix)) { + return id + } + return id.split(prefix)[1] +} + export function getUsersByAppParams(appId: any, otherProps: any = {}) { const prodAppId = getProdAppID(appId) return { @@ -158,20 +238,20 @@ export function getTemplateParams( * Generates a new role ID. * @returns {string} The new role ID which the role doc can be stored under. */ -export function generateRoleID(id: any) { +export function generateRoleID(id?: any) { return `${DocumentType.ROLE}${SEPARATOR}${id || newid()}` } /** * Gets parameters for retrieving a role, this is a utility function for the getDocParams function. */ -export function getRoleParams(roleId = null, otherProps = {}) { +export function getRoleParams(roleId?: string | null, otherProps = {}) { return getDocParams(DocumentType.ROLE, roleId, otherProps) } -export function getStartEndKeyURL(base: any, baseKey: any, tenantId = null) { +export function getStartEndKeyURL(baseKey: any, tenantId?: string) { const tenancy = tenantId ? `${SEPARATOR}${tenantId}` : "" - return `${base}?startkey="${baseKey}${tenancy}"&endkey="${baseKey}${tenancy}${UNICODE_MAX}"` + return `startkey="${baseKey}${tenancy}"&endkey="${baseKey}${tenancy}${UNICODE_MAX}"` } /** @@ -187,22 +267,10 @@ export async function getAllDbs(opts = { efficient: false }) { return allDbs() } 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) - } else { - throw "Cannot connect to CouchDB instance" - } + async function addDbs(queryString?: string) { + const json = await directCouchAllDbs(queryString) + dbs = dbs.concat(json) } - let couchUrl = `${url}/_all_dbs` let tenantId = getTenantId() if (!env.MULTI_TENANCY || (!efficient && tenantId === DEFAULT_TENANT_ID)) { // just get all DBs when: @@ -210,12 +278,12 @@ export async function getAllDbs(opts = { efficient: false }) { // - default tenant // - apps dbs don't contain tenant id // - non-default tenant dbs are filtered out application side in getAllApps - await addDbs(couchUrl) + await addDbs() } else { // get prod apps - await addDbs(getStartEndKeyURL(couchUrl, DocumentType.APP, tenantId)) + await addDbs(getStartEndKeyURL(DocumentType.APP, tenantId)) // get dev apps - await addDbs(getStartEndKeyURL(couchUrl, DocumentType.APP_DEV, tenantId)) + await addDbs(getStartEndKeyURL(DocumentType.APP_DEV, tenantId)) // add global db name dbs.push(getGlobalDBName(tenantId)) } @@ -228,7 +296,12 @@ export async function getAllDbs(opts = { efficient: false }) { * * @return {Promise} returns the app information document stored in each app database. */ -export async function getAllApps({ dev, all, idsOnly, efficient }: any = {}) { +export async function getAllApps({ + dev, + all, + idsOnly, + efficient, +}: any = {}): Promise { let tenantId = getTenantId() if (!env.MULTI_TENANCY && !tenantId) { tenantId = DEFAULT_TENANT_ID @@ -300,35 +373,23 @@ export async function getAllApps({ dev, all, idsOnly, efficient }: any = {}) { * Utility function for getAllApps but filters to production apps only. */ export async function getProdAppIDs() { - return (await getAllApps({ idsOnly: true })).filter( - (id: any) => !isDevAppID(id) - ) + const apps = (await getAllApps({ idsOnly: true })) as string[] + return apps.filter((id: any) => !isDevAppID(id)) } /** * Utility function for the inverse of above. */ export async function getDevAppIDs() { - return (await getAllApps({ idsOnly: true })).filter((id: any) => - isDevAppID(id) - ) + const apps = (await getAllApps({ idsOnly: true })) as string[] + return apps.filter((id: any) => isDevAppID(id)) } export async function dbExists(dbName: any) { - let exists = false 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 + async (db: Database) => { + return await db.exists() }, { skip_setup: true } ) @@ -427,19 +488,13 @@ export const getScopedFullConfig = async function ( )[0] // custom logic for settings doc - if (type === Configs.SETTINGS) { - if (scopedConfig && scopedConfig.doc) { - // overrides affected by environment variables - scopedConfig.doc.config.platformUrl = await getPlatformUrl({ - tenantAware: true, - }) - scopedConfig.doc.config.analyticsEnabled = - await events.analytics.enabled() - } else { + if (type === ConfigType.SETTINGS) { + if (!scopedConfig || !scopedConfig.doc) { // defaults scopedConfig = { doc: { _id: generateConfigID({ type, user, workspace }), + type: ConfigType.SETTINGS, config: { platformUrl: await getPlatformUrl({ tenantAware: true }), analyticsEnabled: await events.analytics.enabled(), @@ -447,6 +502,16 @@ export const getScopedFullConfig = async function ( }, } } + + // will always be true - use assertion function to get type access + if (isSettingsConfig(scopedConfig.doc)) { + // overrides affected by environment + scopedConfig.doc.config.platformUrl = await getPlatformUrl({ + tenantAware: true, + }) + scopedConfig.doc.config.analyticsEnabled = + await events.analytics.enabled() + } } return scopedConfig && scopedConfig.doc @@ -466,7 +531,7 @@ export const getPlatformUrl = async (opts = { tenantAware: true }) => { // get the doc directly instead of with getScopedConfig to prevent loop let settings try { - settings = await db.get(generateConfigID({ type: Configs.SETTINGS })) + settings = await db.get(generateConfigID({ type: ConfigType.SETTINGS })) } catch (e: any) { if (e.status !== 404) { throw e diff --git a/packages/backend-core/src/db/views.ts b/packages/backend-core/src/db/views.ts index f0fff918fc..4a87be0a68 100644 --- a/packages/backend-core/src/db/views.ts +++ b/packages/backend-core/src/db/views.ts @@ -1,8 +1,13 @@ -import { DocumentType, ViewName, DeprecatedViews, SEPARATOR } from "./utils" +import { + DocumentType, + ViewName, + DeprecatedViews, + SEPARATOR, + StaticDatabases, +} from "../constants" import { getGlobalDB } from "../context" -import PouchDB from "pouchdb" -import { StaticDatabases } from "./constants" import { doWithDB } from "./" +import { Database, DatabaseQueryOpts } from "@budibase/types" const DESIGN_DB = "_design/database" @@ -19,7 +24,7 @@ interface DesignDocument { views: any } -async function removeDeprecated(db: PouchDB.Database, viewName: ViewName) { +async function removeDeprecated(db: Database, viewName: ViewName) { // @ts-ignore if (!DeprecatedViews[viewName]) { return @@ -70,16 +75,13 @@ export const createAccountEmailView = async () => { emit(doc.email.toLowerCase(), doc._id) } }` - await doWithDB( - StaticDatabases.PLATFORM_INFO.name, - async (db: PouchDB.Database) => { - await createView(db, viewJs, ViewName.ACCOUNT_BY_EMAIL) - } - ) + await doWithDB(StaticDatabases.PLATFORM_INFO.name, async (db: Database) => { + await createView(db, viewJs, ViewName.ACCOUNT_BY_EMAIL) + }) } export const createUserAppView = async () => { - const db = getGlobalDB() as PouchDB.Database + const db = getGlobalDB() const viewJs = `function(doc) { if (doc._id.startsWith("${DocumentType.USER}${SEPARATOR}") && doc.roles) { for (let prodAppId of Object.keys(doc.roles)) { @@ -117,12 +119,9 @@ export const createPlatformUserView = async () => { emit(doc._id.toLowerCase(), doc._id) } }` - await doWithDB( - StaticDatabases.PLATFORM_INFO.name, - async (db: PouchDB.Database) => { - await createView(db, viewJs, ViewName.PLATFORM_USERS_LOWERCASE) - } - ) + await doWithDB(StaticDatabases.PLATFORM_INFO.name, async (db: Database) => { + await createView(db, viewJs, ViewName.PLATFORM_USERS_LOWERCASE) + }) } export interface QueryViewOptions { @@ -131,25 +130,29 @@ export interface QueryViewOptions { export const queryView = async ( viewName: ViewName, - params: PouchDB.Query.Options, - db: PouchDB.Database, + params: DatabaseQueryOpts, + db: Database, createFunc: any, opts?: QueryViewOptions ): Promise => { try { - let response = await db.query(`database/${viewName}`, params) + let response = await db.query(`database/${viewName}`, params) const rows = response.rows - const docs = rows.map(row => (params.include_docs ? row.doc : row.value)) + const docs = rows.map((row: any) => + params.include_docs ? row.doc : row.value + ) // if arrayResponse has been requested, always return array regardless of length if (opts?.arrayResponse) { - return docs + return docs as T[] } else { // return the single document if there is only one - return docs.length <= 1 ? docs[0] : docs + return docs.length <= 1 ? (docs[0] as T) : (docs as T[]) } } catch (err: any) { - if (err != null && err.name === "not_found") { + const pouchNotFound = err && err.name === "not_found" + const couchNotFound = err && err.status === 404 + if (pouchNotFound || couchNotFound) { await removeDeprecated(db, viewName) await createFunc() return queryView(viewName, params, db, createFunc, opts) @@ -161,7 +164,7 @@ export const queryView = async ( export const queryPlatformView = async ( viewName: ViewName, - params: PouchDB.Query.Options, + params: DatabaseQueryOpts, opts?: QueryViewOptions ): Promise => { const CreateFuncByName: any = { @@ -169,19 +172,16 @@ export const queryPlatformView = async ( [ViewName.PLATFORM_USERS_LOWERCASE]: createPlatformUserView, } - return doWithDB( - StaticDatabases.PLATFORM_INFO.name, - async (db: PouchDB.Database) => { - const createFn = CreateFuncByName[viewName] - return queryView(viewName, params, db, createFn, opts) - } - ) + return doWithDB(StaticDatabases.PLATFORM_INFO.name, async (db: Database) => { + const createFn = CreateFuncByName[viewName] + return queryView(viewName, params, db, createFn, opts) + }) } export const queryGlobalView = async ( viewName: ViewName, - params: PouchDB.Query.Options, - db?: PouchDB.Database, + params: DatabaseQueryOpts, + db?: Database, opts?: QueryViewOptions ): Promise => { const CreateFuncByName: any = { @@ -192,8 +192,8 @@ export const queryGlobalView = async ( } // can pass DB in if working with something specific if (!db) { - db = getGlobalDB() as PouchDB.Database + db = getGlobalDB() } const createFn = CreateFuncByName[viewName] - return queryView(viewName, params, db, createFn, opts) + return queryView(viewName, params, db!, createFn, opts) } diff --git a/packages/backend-core/src/environment.ts b/packages/backend-core/src/environment.ts index 6e2ac94be9..d742ca1cc9 100644 --- a/packages/backend-core/src/environment.ts +++ b/packages/backend-core/src/environment.ts @@ -1,9 +1,13 @@ function isTest() { - return ( - process.env.NODE_ENV === "jest" || - process.env.NODE_ENV === "cypress" || - process.env.JEST_WORKER_ID != null - ) + return isCypress() || isJest() +} + +function isJest() { + return !!(process.env.NODE_ENV === "jest" || process.env.JEST_WORKER_ID) +} + +function isCypress() { + return process.env.NODE_ENV === "cypress" } function isDev() { @@ -21,15 +25,19 @@ const DefaultBucketName = { APPS: "prod-budi-app-assets", TEMPLATES: "templates", GLOBAL: "global", - CLOUD: "prod-budi-tenant-uploads", PLUGINS: "plugins", } -const env = { +const environment = { isTest, + isJest, isDev, + isProd: () => { + return !isDev() + }, JS_BCRYPT: process.env.JS_BCRYPT, JWT_SECRET: process.env.JWT_SECRET, + ENCRYPTION_KEY: process.env.ENCRYPTION_KEY, 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, @@ -42,6 +50,7 @@ const env = { MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY, AWS_REGION: process.env.AWS_REGION, MINIO_URL: process.env.MINIO_URL, + MINIO_ENABLED: process.env.MINIO_ENABLED || 1, INTERNAL_API_KEY: process.env.INTERNAL_API_KEY, MULTI_TENANCY: process.env.MULTI_TENANCY, ACCOUNT_PORTAL_URL: @@ -54,6 +63,9 @@ const env = { POSTHOG_TOKEN: process.env.POSTHOG_TOKEN, ENABLE_ANALYTICS: process.env.ENABLE_ANALYTICS, TENANT_FEATURE_FLAGS: process.env.TENANT_FEATURE_FLAGS, + CLOUDFRONT_CDN: process.env.CLOUDFRONT_CDN, + CLOUDFRONT_PRIVATE_KEY_64: process.env.CLOUDFRONT_PRIVATE_KEY_64, + CLOUDFRONT_PUBLIC_KEY_ID: process.env.CLOUDFRONT_PUBLIC_KEY_ID, BACKUPS_BUCKET_NAME: process.env.BACKUPS_BUCKET_NAME || DefaultBucketName.BACKUPS, APPS_BUCKET_NAME: process.env.APPS_BUCKET_NAME || DefaultBucketName.APPS, @@ -61,32 +73,34 @@ const env = { process.env.TEMPLATES_BUCKET_NAME || DefaultBucketName.TEMPLATES, GLOBAL_BUCKET_NAME: process.env.GLOBAL_BUCKET_NAME || DefaultBucketName.GLOBAL, - GLOBAL_CLOUD_BUCKET_NAME: - process.env.GLOBAL_CLOUD_BUCKET_NAME || DefaultBucketName.CLOUD, PLUGIN_BUCKET_NAME: process.env.PLUGIN_BUCKET_NAME || DefaultBucketName.PLUGINS, 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, LOG_LEVEL: process.env.LOG_LEVEL, SESSION_UPDATE_PERIOD: process.env.SESSION_UPDATE_PERIOD, DEPLOYMENT_ENVIRONMENT: process.env.DEPLOYMENT_ENVIRONMENT || "docker-compose", _set(key: any, value: any) { process.env[key] = value - module.exports[key] = value + // @ts-ignore + environment[key] = value }, } // clean up any environment variable edge cases -for (let [key, value] of Object.entries(env)) { +for (let [key, value] of Object.entries(environment)) { // handle the edge case of "0" to disable an environment variable if (value === "0") { // @ts-ignore - env[key] = 0 + environment[key] = 0 + } + // handle the edge case of "false" to disable an environment variable + if (value === "false") { + // @ts-ignore + environment[key] = 0 } } -export = env +export default environment diff --git a/packages/backend-core/src/errors/errors.ts b/packages/backend-core/src/errors/errors.ts new file mode 100644 index 0000000000..83e2ab5072 --- /dev/null +++ b/packages/backend-core/src/errors/errors.ts @@ -0,0 +1,37 @@ +import * as licensing from "./licensing" + +// combine all error codes into single object + +export const codes = { + ...licensing.codes, +} + +// combine all error types +export const types = [licensing.type] + +// combine all error contexts +const context = { + ...licensing.context, +} + +// derive a public error message using codes, types and any custom contexts +export const getPublicError = (err: any) => { + 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 +} diff --git a/packages/backend-core/src/errors/index.ts b/packages/backend-core/src/errors/index.ts index be6657093d..814d836590 100644 --- a/packages/backend-core/src/errors/index.ts +++ b/packages/backend-core/src/errors/index.ts @@ -1,47 +1,3 @@ -import { HTTPError } from "./http" -import { UsageLimitError, FeatureDisabledError } from "./licensing" -import * as licensing from "./licensing" - -const codes = { - ...licensing.codes, -} - -const types = [licensing.type] - -const context = { - ...licensing.context, -} - -const getPublicError = (err: any) => { - 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 -} - -const pkg = { - codes, - types, - errors: { - UsageLimitError, - FeatureDisabledError, - HTTPError, - }, - getPublicError, -} - -export = pkg +export * from "./errors" +export { UsageLimitError, FeatureDisabledError } from "./licensing" +export { HTTPError } from "./http" diff --git a/packages/backend-core/src/events/analytics.ts b/packages/backend-core/src/events/analytics.ts index 802b6d6314..f621a9c98b 100644 --- a/packages/backend-core/src/events/analytics.ts +++ b/packages/backend-core/src/events/analytics.ts @@ -1,8 +1,8 @@ import env from "../environment" -import tenancy from "../tenancy" +import * as tenancy from "../tenancy" import * as dbUtils from "../db/utils" -import { Configs } from "../constants" -import { withCache, TTL, CacheKeys } from "../cache/generic" +import { Config } from "../constants" +import { withCache, TTL, CacheKey } from "../cache" export const enabled = async () => { // cloud - always use the environment variable @@ -13,7 +13,7 @@ export const enabled = async () => { // self host - prefer the settings doc // use cache as events have high throughput const enabledInDB = await withCache( - CacheKeys.ANALYTICS_ENABLED, + CacheKey.ANALYTICS_ENABLED, TTL.ONE_DAY, async () => { const settings = await getSettingsDoc() @@ -45,9 +45,7 @@ const getSettingsDoc = async () => { const db = tenancy.getGlobalDB() let settings try { - settings = await db.get( - dbUtils.generateConfigID({ type: Configs.SETTINGS }) - ) + settings = await db.get(dbUtils.generateConfigID({ type: Config.SETTINGS })) } catch (e: any) { if (e.status !== 404) { throw e diff --git a/packages/backend-core/src/events/backfill.ts b/packages/backend-core/src/events/backfill.ts index e4577c5ab4..c8025a8e4e 100644 --- a/packages/backend-core/src/events/backfill.ts +++ b/packages/backend-core/src/events/backfill.ts @@ -21,7 +21,7 @@ import { AppCreatedEvent, } from "@budibase/types" import * as context from "../context" -import { CacheKeys } from "../cache/generic" +import { CacheKey } from "../cache/generic" import * as cache from "../cache/generic" // LIFECYCLE @@ -48,18 +48,18 @@ export const end = async () => { // CRUD const getBackfillMetadata = async (): Promise => { - return cache.get(CacheKeys.BACKFILL_METADATA) + return cache.get(CacheKey.BACKFILL_METADATA) } const saveBackfillMetadata = async ( backfill: BackfillMetadata ): Promise => { // no TTL - deleted by backfill - return cache.store(CacheKeys.BACKFILL_METADATA, backfill) + return cache.store(CacheKey.BACKFILL_METADATA, backfill) } const deleteBackfillMetadata = async (): Promise => { - await cache.delete(CacheKeys.BACKFILL_METADATA) + await cache.destroy(CacheKey.BACKFILL_METADATA) } const clearEvents = async () => { @@ -70,7 +70,7 @@ const clearEvents = async () => { for (const key of keys) { // delete each key // don't use tenancy, already in the key - await cache.delete(key, { useTenancy: false }) + await cache.destroy(key, { useTenancy: false }) } } @@ -167,7 +167,7 @@ const getEventKey = (event?: Event, properties?: any) => { const tenantId = context.getTenantId() if (event) { - eventKey = `${CacheKeys.EVENTS}:${tenantId}:${event}` + eventKey = `${CacheKey.EVENTS}:${tenantId}:${event}` // use some properties to make the key more unique const custom = CUSTOM_PROPERTY_SUFFIX[event] @@ -176,7 +176,7 @@ const getEventKey = (event?: Event, properties?: any) => { eventKey = `${eventKey}:${suffix}` } } else { - eventKey = `${CacheKeys.EVENTS}:${tenantId}:*` + eventKey = `${CacheKey.EVENTS}:${tenantId}:*` } return eventKey diff --git a/packages/backend-core/src/events/events.ts b/packages/backend-core/src/events/events.ts index cda90d12c9..01928221a0 100644 --- a/packages/backend-core/src/events/events.ts +++ b/packages/backend-core/src/events/events.ts @@ -1,6 +1,6 @@ import { Event } from "@budibase/types" import { processors } from "./processors" -import * as identification from "./identification" +import identification from "./identification" import * as backfill from "./backfill" export const publishEvent = async ( diff --git a/packages/backend-core/src/events/identification.ts b/packages/backend-core/src/events/identification.ts index a29a6821cd..8ac22b471c 100644 --- a/packages/backend-core/src/events/identification.ts +++ b/packages/backend-core/src/events/identification.ts @@ -19,10 +19,10 @@ import { } from "@budibase/types" import { processors } from "./processors" import * as dbUtils from "../db/utils" -import { Configs } from "../constants" -import * as hashing from "../hashing" +import { Config } from "../constants" +import { newid } from "../utils" import * as installation from "../installation" -import { withCache, TTL, CacheKeys } from "../cache/generic" +import { withCache, TTL, CacheKey } from "../cache/generic" const pkg = require("../../package.json") @@ -33,7 +33,7 @@ const pkg = require("../../package.json") * - tenant * - installation */ -export const getCurrentIdentity = async (): Promise => { +const getCurrentIdentity = async (): Promise => { let identityContext = identityCtx.getIdentity() const environment = getDeploymentEnvironment() @@ -94,7 +94,7 @@ export const getCurrentIdentity = async (): Promise => { } } -export const identifyInstallationGroup = async ( +const identifyInstallationGroup = async ( installId: string, timestamp?: string | number ): Promise => { @@ -118,7 +118,7 @@ export const identifyInstallationGroup = async ( await identify({ ...group, id: `$${type}_${id}` }, timestamp) } -export const identifyTenantGroup = async ( +const identifyTenantGroup = async ( tenantId: string, account: Account | undefined, timestamp?: string | number @@ -156,7 +156,7 @@ export const identifyTenantGroup = async ( await identify({ ...group, id: `$${type}_${id}` }, timestamp) } -export const identifyUser = async ( +const identifyUser = async ( user: User, account: CloudAccount | undefined, timestamp?: string | number @@ -191,7 +191,7 @@ export const identifyUser = async ( await identify(identity, timestamp) } -export const identifyAccount = async (account: Account) => { +const identifyAccount = async (account: Account) => { let id = account.accountId const tenantId = account.tenantId let type = IdentityType.USER @@ -224,17 +224,11 @@ export const identifyAccount = async (account: Account) => { await identify(identity) } -export const identify = async ( - identity: Identity, - timestamp?: string | number -) => { +const identify = async (identity: Identity, timestamp?: string | number) => { await processors.identify(identity, timestamp) } -export const identifyGroup = async ( - group: Group, - timestamp?: string | number -) => { +const identifyGroup = async (group: Group, timestamp?: string | number) => { await processors.identifyGroup(group, timestamp) } @@ -250,7 +244,7 @@ const getHostingFromEnv = () => { return env.SELF_HOSTED ? Hosting.SELF : Hosting.CLOUD } -export const getInstallationId = async () => { +const getInstallationId = async () => { if (isAccountPortal()) { return "account-portal" } @@ -270,17 +264,17 @@ const getEventTenantId = async (tenantId: string): Promise => { 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 () => { + return withCache(CacheKey.UNIQUE_TENANT_ID, TTL.ONE_DAY, async () => { const db = context.getGlobalDB() const config: SettingsConfig = await dbUtils.getScopedFullConfig(db, { - type: Configs.SETTINGS, + type: Config.SETTINGS, }) let uniqueTenantId: string if (config.config.uniqueTenantId) { return config.config.uniqueTenantId } else { - uniqueTenantId = `${hashing.newid()}_${tenantId}` + uniqueTenantId = `${newid()}_${tenantId}` config.config.uniqueTenantId = uniqueTenantId await db.put(config) return uniqueTenantId @@ -300,3 +294,14 @@ const formatDistinctId = (id: string, type: IdentityType) => { return id } } + +export default { + getCurrentIdentity, + identifyInstallationGroup, + identifyTenantGroup, + identifyUser, + identifyAccount, + identify, + identifyGroup, + getInstallationId, +} diff --git a/packages/backend-core/src/events/index.ts b/packages/backend-core/src/events/index.ts index f94c8b0267..d0d59a5b22 100644 --- a/packages/backend-core/src/events/index.ts +++ b/packages/backend-core/src/events/index.ts @@ -1,7 +1,7 @@ export * from "./publishers" export * as processors from "./processors" export * as analytics from "./analytics" -export * as identification from "./identification" +export { default as identification } from "./identification" export * as backfillCache from "./backfill" import { processors } from "./processors" diff --git a/packages/backend-core/src/events/processors/LoggingProcessor.ts b/packages/backend-core/src/events/processors/LoggingProcessor.ts index d41a82fbb4..6bb691a83a 100644 --- a/packages/backend-core/src/events/processors/LoggingProcessor.ts +++ b/packages/backend-core/src/events/processors/LoggingProcessor.ts @@ -23,7 +23,7 @@ export default class LoggingProcessor implements EventProcessor { return } let timestampString = getTimestampString(timestamp) - let message = `[audit] [tenant=${identity.tenantId}] [identityType=${identity.type}] [identity=${identity.id}] ${timestampString} ${event} ` + let message = `[audit] [identityType=${identity.type}] ${timestampString} ${event} ` if (env.isDev()) { message = message + `[debug: [properties=${JSON.stringify(properties)}] ]` } diff --git a/packages/backend-core/src/events/processors/posthog/rateLimiting.ts b/packages/backend-core/src/events/processors/posthog/rateLimiting.ts index 9c7b7876d6..89da10defa 100644 --- a/packages/backend-core/src/events/processors/posthog/rateLimiting.ts +++ b/packages/backend-core/src/events/processors/posthog/rateLimiting.ts @@ -1,5 +1,5 @@ import { Event } from "@budibase/types" -import { CacheKeys, TTL } from "../../../cache/generic" +import { CacheKey, TTL } from "../../../cache/generic" import * as cache from "../../../cache/generic" import * as context from "../../../context" @@ -74,7 +74,7 @@ export const limited = async (event: Event): Promise => { } const eventKey = (event: RateLimitedEvent) => { - let key = `${CacheKeys.EVENTS_RATE_LIMIT}:${event}` + let key = `${CacheKey.EVENTS_RATE_LIMIT}:${event}` if (isPerApp(event)) { key = key + ":" + context.getAppId() } diff --git a/packages/backend-core/src/events/processors/posthog/tests/PosthogProcessor.spec.ts b/packages/backend-core/src/events/processors/posthog/tests/PosthogProcessor.spec.ts index d14b697966..349a0427ac 100644 --- a/packages/backend-core/src/events/processors/posthog/tests/PosthogProcessor.spec.ts +++ b/packages/backend-core/src/events/processors/posthog/tests/PosthogProcessor.spec.ts @@ -1,9 +1,9 @@ -import "../../../../../tests/utilities/TestConfiguration" +import "../../../../../tests" import PosthogProcessor from "../PosthogProcessor" import { Event, IdentityType, Hosting } from "@budibase/types" const tk = require("timekeeper") import * as cache from "../../../../cache/generic" -import { CacheKeys } from "../../../../cache/generic" +import { CacheKey } from "../../../../cache/generic" import * as context from "../../../../context" const newIdentity = () => { @@ -19,7 +19,7 @@ describe("PosthogProcessor", () => { beforeEach(async () => { jest.clearAllMocks() await cache.bustCache( - `${CacheKeys.EVENTS_RATE_LIMIT}:${Event.SERVED_BUILDER}` + `${CacheKey.EVENTS_RATE_LIMIT}:${Event.SERVED_BUILDER}` ) }) @@ -89,7 +89,7 @@ describe("PosthogProcessor", () => { await processor.processEvent(Event.SERVED_BUILDER, identity, properties) await cache.bustCache( - `${CacheKeys.EVENTS_RATE_LIMIT}:${Event.SERVED_BUILDER}` + `${CacheKey.EVENTS_RATE_LIMIT}:${Event.SERVED_BUILDER}` ) tk.freeze(new Date(2022, 0, 1, 14, 0)) diff --git a/packages/backend-core/src/events/publishers/account.ts b/packages/backend-core/src/events/publishers/account.ts index 3f1a8a9161..d337e404ef 100644 --- a/packages/backend-core/src/events/publishers/account.ts +++ b/packages/backend-core/src/events/publishers/account.ts @@ -7,23 +7,29 @@ import { AccountVerifiedEvent, } from "@budibase/types" -export async function created(account: Account) { +async function created(account: Account) { const properties: AccountCreatedEvent = { tenantId: account.tenantId, } await publishEvent(Event.ACCOUNT_CREATED, properties) } -export async function deleted(account: Account) { +async function deleted(account: Account) { const properties: AccountDeletedEvent = { tenantId: account.tenantId, } await publishEvent(Event.ACCOUNT_DELETED, properties) } -export async function verified(account: Account) { +async function verified(account: Account) { const properties: AccountVerifiedEvent = { tenantId: account.tenantId, } await publishEvent(Event.ACCOUNT_VERIFIED, properties) } + +export default { + created, + deleted, + verified, +} diff --git a/packages/backend-core/src/events/publishers/app.ts b/packages/backend-core/src/events/publishers/app.ts index dd77b0b8a2..90da21f3f5 100644 --- a/packages/backend-core/src/events/publishers/app.ts +++ b/packages/backend-core/src/events/publishers/app.ts @@ -15,7 +15,7 @@ import { AppExportedEvent, } from "@budibase/types" -export const created = async (app: App, timestamp?: string | number) => { +const created = async (app: App, timestamp?: string | number) => { const properties: AppCreatedEvent = { appId: app.appId, version: app.version, @@ -23,7 +23,7 @@ export const created = async (app: App, timestamp?: string | number) => { await publishEvent(Event.APP_CREATED, properties, timestamp) } -export async function updated(app: App) { +async function updated(app: App) { const properties: AppUpdatedEvent = { appId: app.appId, version: app.version, @@ -31,35 +31,35 @@ export async function updated(app: App) { await publishEvent(Event.APP_UPDATED, properties) } -export async function deleted(app: App) { +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) { +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) { +async function unpublished(app: App) { const properties: AppUnpublishedEvent = { appId: app.appId, } await publishEvent(Event.APP_UNPUBLISHED, properties) } -export async function fileImported(app: App) { +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) { +async function templateImported(app: App, templateKey: string) { const properties: AppTemplateImportedEvent = { appId: app.appId, templateKey, @@ -67,7 +67,7 @@ export async function templateImported(app: App, templateKey: string) { await publishEvent(Event.APP_TEMPLATE_IMPORTED, properties) } -export async function versionUpdated( +async function versionUpdated( app: App, currentVersion: string, updatedToVersion: string @@ -80,7 +80,7 @@ export async function versionUpdated( await publishEvent(Event.APP_VERSION_UPDATED, properties) } -export async function versionReverted( +async function versionReverted( app: App, currentVersion: string, revertedToVersion: string @@ -93,16 +93,30 @@ export async function versionReverted( await publishEvent(Event.APP_VERSION_REVERTED, properties) } -export async function reverted(app: App) { +async function reverted(app: App) { const properties: AppRevertedEvent = { appId: app.appId, } await publishEvent(Event.APP_REVERTED, properties) } -export async function exported(app: App) { +async function exported(app: App) { const properties: AppExportedEvent = { appId: app.appId, } await publishEvent(Event.APP_EXPORTED, properties) } + +export default { + created, + updated, + deleted, + published, + unpublished, + fileImported, + templateImported, + versionUpdated, + versionReverted, + reverted, + exported, +} diff --git a/packages/backend-core/src/events/publishers/auth.ts b/packages/backend-core/src/events/publishers/auth.ts index 93378501f3..4436045599 100644 --- a/packages/backend-core/src/events/publishers/auth.ts +++ b/packages/backend-core/src/events/publishers/auth.ts @@ -12,7 +12,7 @@ import { } from "@budibase/types" import { identification } from ".." -export async function login(source: LoginSource) { +async function login(source: LoginSource) { const identity = await identification.getCurrentIdentity() const properties: LoginEvent = { userId: identity.id, @@ -21,7 +21,7 @@ export async function login(source: LoginSource) { await publishEvent(Event.AUTH_LOGIN, properties) } -export async function logout() { +async function logout() { const identity = await identification.getCurrentIdentity() const properties: LogoutEvent = { userId: identity.id, @@ -29,30 +29,39 @@ export async function logout() { await publishEvent(Event.AUTH_LOGOUT, properties) } -export async function SSOCreated(type: SSOType, timestamp?: string | number) { +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) { +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) { +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) { +async function SSODeactivated(type: SSOType) { const properties: SSODeactivatedEvent = { type, } await publishEvent(Event.AUTH_SSO_DEACTIVATED, properties) } + +export default { + login, + logout, + SSOCreated, + SSOUpdated, + SSOActivated, + SSODeactivated, +} diff --git a/packages/backend-core/src/events/publishers/automation.ts b/packages/backend-core/src/events/publishers/automation.ts index 95f9cb8db6..6eb36ab067 100644 --- a/packages/backend-core/src/events/publishers/automation.ts +++ b/packages/backend-core/src/events/publishers/automation.ts @@ -12,10 +12,7 @@ import { AutomationsRunEvent, } from "@budibase/types" -export async function created( - automation: Automation, - timestamp?: string | number -) { +async function created(automation: Automation, timestamp?: string | number) { const properties: AutomationCreatedEvent = { appId: automation.appId, automationId: automation._id as string, @@ -25,7 +22,7 @@ export async function created( await publishEvent(Event.AUTOMATION_CREATED, properties, timestamp) } -export async function triggerUpdated(automation: Automation) { +async function triggerUpdated(automation: Automation) { const properties: AutomationTriggerUpdatedEvent = { appId: automation.appId, automationId: automation._id as string, @@ -35,7 +32,7 @@ export async function triggerUpdated(automation: Automation) { await publishEvent(Event.AUTOMATION_TRIGGER_UPDATED, properties) } -export async function deleted(automation: Automation) { +async function deleted(automation: Automation) { const properties: AutomationDeletedEvent = { appId: automation.appId, automationId: automation._id as string, @@ -45,7 +42,7 @@ export async function deleted(automation: Automation) { await publishEvent(Event.AUTOMATION_DELETED, properties) } -export async function tested(automation: Automation) { +async function tested(automation: Automation) { const properties: AutomationTestedEvent = { appId: automation.appId, automationId: automation._id as string, @@ -55,14 +52,14 @@ export async function tested(automation: Automation) { await publishEvent(Event.AUTOMATION_TESTED, properties) } -export const run = async (count: number, timestamp?: string | number) => { +const run = async (count: number, timestamp?: string | number) => { const properties: AutomationsRunEvent = { count, } await publishEvent(Event.AUTOMATIONS_RUN, properties, timestamp) } -export async function stepCreated( +async function stepCreated( automation: Automation, step: AutomationStep, timestamp?: string | number @@ -72,23 +69,30 @@ export async function stepCreated( automationId: automation._id as string, triggerId: automation.definition?.trigger?.id, triggerType: automation.definition?.trigger?.stepId, - stepId: step.id, + stepId: step.id!, stepType: step.stepId, } await publishEvent(Event.AUTOMATION_STEP_CREATED, properties, timestamp) } -export async function stepDeleted( - automation: Automation, - step: AutomationStep -) { +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, + stepId: step.id!, stepType: step.stepId, } await publishEvent(Event.AUTOMATION_STEP_DELETED, properties) } + +export default { + created, + triggerUpdated, + deleted, + tested, + run, + stepCreated, + stepDeleted, +} diff --git a/packages/backend-core/src/events/publishers/backfill.ts b/packages/backend-core/src/events/publishers/backfill.ts index c16b3cc9dc..f2f6c7add8 100644 --- a/packages/backend-core/src/events/publishers/backfill.ts +++ b/packages/backend-core/src/events/publishers/backfill.ts @@ -8,18 +8,18 @@ import { InstallationBackfillSucceededEvent, InstallationBackfillFailedEvent, } from "@budibase/types" -const env = require("../../environment") +import env from "../../environment" const shouldSkip = !env.SELF_HOSTED && !env.isDev() -export async function appSucceeded(properties: AppBackfillSucceededEvent) { +async function appSucceeded(properties: AppBackfillSucceededEvent) { if (shouldSkip) { return } await publishEvent(Event.APP_BACKFILL_SUCCEEDED, properties) } -export async function appFailed(error: any) { +async function appFailed(error: any) { if (shouldSkip) { return } @@ -29,16 +29,14 @@ export async function appFailed(error: any) { await publishEvent(Event.APP_BACKFILL_FAILED, properties) } -export async function tenantSucceeded( - properties: TenantBackfillSucceededEvent -) { +async function tenantSucceeded(properties: TenantBackfillSucceededEvent) { if (shouldSkip) { return } await publishEvent(Event.TENANT_BACKFILL_SUCCEEDED, properties) } -export async function tenantFailed(error: any) { +async function tenantFailed(error: any) { if (shouldSkip) { return } @@ -48,7 +46,7 @@ export async function tenantFailed(error: any) { await publishEvent(Event.TENANT_BACKFILL_FAILED, properties) } -export async function installationSucceeded() { +async function installationSucceeded() { if (shouldSkip) { return } @@ -56,7 +54,7 @@ export async function installationSucceeded() { await publishEvent(Event.INSTALLATION_BACKFILL_SUCCEEDED, properties) } -export async function installationFailed(error: any) { +async function installationFailed(error: any) { if (shouldSkip) { return } @@ -65,3 +63,12 @@ export async function installationFailed(error: any) { } await publishEvent(Event.INSTALLATION_BACKFILL_FAILED, properties) } + +export default { + appSucceeded, + appFailed, + tenantSucceeded, + tenantFailed, + installationSucceeded, + installationFailed, +} diff --git a/packages/backend-core/src/events/publishers/backup.ts b/packages/backend-core/src/events/publishers/backup.ts new file mode 100644 index 0000000000..12263fe1ff --- /dev/null +++ b/packages/backend-core/src/events/publishers/backup.ts @@ -0,0 +1,39 @@ +import { + AppBackup, + AppBackupRestoreEvent, + AppBackupTriggeredEvent, + AppBackupTrigger, + AppBackupType, + Event, +} from "@budibase/types" +import { publishEvent } from "../events" + +async function appBackupRestored(backup: AppBackup) { + const properties: AppBackupRestoreEvent = { + appId: backup.appId, + restoreId: backup._id!, + backupCreatedAt: backup.timestamp, + } + + await publishEvent(Event.APP_BACKUP_RESTORED, properties) +} + +async function appBackupTriggered( + appId: string, + backupId: string, + type: AppBackupType, + trigger: AppBackupTrigger +) { + const properties: AppBackupTriggeredEvent = { + appId: appId, + backupId, + type, + trigger, + } + await publishEvent(Event.APP_BACKUP_TRIGGERED, properties) +} + +export default { + appBackupRestored, + appBackupTriggered, +} diff --git a/packages/backend-core/src/events/publishers/datasource.ts b/packages/backend-core/src/events/publishers/datasource.ts index d3ea7402f9..39a81ff1bc 100644 --- a/packages/backend-core/src/events/publishers/datasource.ts +++ b/packages/backend-core/src/events/publishers/datasource.ts @@ -14,10 +14,7 @@ function isCustom(datasource: Datasource) { return !sources.includes(datasource.source) } -export async function created( - datasource: Datasource, - timestamp?: string | number -) { +async function created(datasource: Datasource, timestamp?: string | number) { const properties: DatasourceCreatedEvent = { datasourceId: datasource._id as string, source: datasource.source, @@ -26,7 +23,7 @@ export async function created( await publishEvent(Event.DATASOURCE_CREATED, properties, timestamp) } -export async function updated(datasource: Datasource) { +async function updated(datasource: Datasource) { const properties: DatasourceUpdatedEvent = { datasourceId: datasource._id as string, source: datasource.source, @@ -35,7 +32,7 @@ export async function updated(datasource: Datasource) { await publishEvent(Event.DATASOURCE_UPDATED, properties) } -export async function deleted(datasource: Datasource) { +async function deleted(datasource: Datasource) { const properties: DatasourceDeletedEvent = { datasourceId: datasource._id as string, source: datasource.source, @@ -43,3 +40,9 @@ export async function deleted(datasource: Datasource) { } await publishEvent(Event.DATASOURCE_DELETED, properties) } + +export default { + created, + updated, + deleted, +} diff --git a/packages/backend-core/src/events/publishers/email.ts b/packages/backend-core/src/events/publishers/email.ts index 42c5fb4d4c..df9e41b39e 100644 --- a/packages/backend-core/src/events/publishers/email.ts +++ b/packages/backend-core/src/events/publishers/email.ts @@ -1,12 +1,17 @@ import { publishEvent } from "../events" import { Event, SMTPCreatedEvent, SMTPUpdatedEvent } from "@budibase/types" -export async function SMTPCreated(timestamp?: string | number) { +async function SMTPCreated(timestamp?: string | number) { const properties: SMTPCreatedEvent = {} await publishEvent(Event.EMAIL_SMTP_CREATED, properties, timestamp) } -export async function SMTPUpdated() { +async function SMTPUpdated() { const properties: SMTPUpdatedEvent = {} await publishEvent(Event.EMAIL_SMTP_UPDATED, properties) } + +export default { + SMTPCreated, + SMTPUpdated, +} diff --git a/packages/backend-core/src/events/publishers/environmentVariable.ts b/packages/backend-core/src/events/publishers/environmentVariable.ts new file mode 100644 index 0000000000..d28e259b82 --- /dev/null +++ b/packages/backend-core/src/events/publishers/environmentVariable.ts @@ -0,0 +1,38 @@ +import { + Event, + EnvironmentVariableCreatedEvent, + EnvironmentVariableDeletedEvent, + EnvironmentVariableUpgradePanelOpenedEvent, +} from "@budibase/types" +import { publishEvent } from "../events" + +async function created(name: string, environments: string[]) { + const properties: EnvironmentVariableCreatedEvent = { + name, + environments, + } + await publishEvent(Event.ENVIRONMENT_VARIABLE_CREATED, properties) +} + +async function deleted(name: string) { + const properties: EnvironmentVariableDeletedEvent = { + name, + } + await publishEvent(Event.ENVIRONMENT_VARIABLE_DELETED, properties) +} + +async function upgradePanelOpened(userId: string) { + const properties: EnvironmentVariableUpgradePanelOpenedEvent = { + userId, + } + await publishEvent( + Event.ENVIRONMENT_VARIABLE_UPGRADE_PANEL_OPENED, + properties + ) +} + +export default { + created, + deleted, + upgradePanelOpened, +} diff --git a/packages/backend-core/src/events/publishers/group.ts b/packages/backend-core/src/events/publishers/group.ts index b4fd0d1469..d79920562b 100644 --- a/packages/backend-core/src/events/publishers/group.ts +++ b/packages/backend-core/src/events/publishers/group.ts @@ -11,28 +11,28 @@ import { UserGroupRoles, } from "@budibase/types" -export async function created(group: UserGroup, timestamp?: number) { +async function created(group: UserGroup, timestamp?: number) { const properties: GroupCreatedEvent = { groupId: group._id as string, } await publishEvent(Event.USER_GROUP_CREATED, properties, timestamp) } -export async function updated(group: UserGroup) { +async function updated(group: UserGroup) { const properties: GroupUpdatedEvent = { groupId: group._id as string, } await publishEvent(Event.USER_GROUP_UPDATED, properties) } -export async function deleted(group: UserGroup) { +async function deleted(group: UserGroup) { const properties: GroupDeletedEvent = { groupId: group._id as string, } await publishEvent(Event.USER_GROUP_DELETED, properties) } -export async function usersAdded(count: number, group: UserGroup) { +async function usersAdded(count: number, group: UserGroup) { const properties: GroupUsersAddedEvent = { count, groupId: group._id as string, @@ -40,7 +40,7 @@ export async function usersAdded(count: number, group: UserGroup) { await publishEvent(Event.USER_GROUP_USERS_ADDED, properties) } -export async function usersDeleted(count: number, group: UserGroup) { +async function usersDeleted(count: number, group: UserGroup) { const properties: GroupUsersDeletedEvent = { count, groupId: group._id as string, @@ -48,7 +48,7 @@ export async function usersDeleted(count: number, group: UserGroup) { await publishEvent(Event.USER_GROUP_USERS_REMOVED, properties) } -export async function createdOnboarding(groupId: string) { +async function createdOnboarding(groupId: string) { const properties: GroupAddedOnboardingEvent = { groupId: groupId, onboarding: true, @@ -56,9 +56,19 @@ export async function createdOnboarding(groupId: string) { await publishEvent(Event.USER_GROUP_ONBOARDING, properties) } -export async function permissionsEdited(roles: UserGroupRoles) { +async function permissionsEdited(roles: UserGroupRoles) { const properties: UserGroupRoles = { ...roles, } await publishEvent(Event.USER_GROUP_PERMISSIONS_EDITED, properties) } + +export default { + created, + updated, + deleted, + usersAdded, + usersDeleted, + createdOnboarding, + permissionsEdited, +} diff --git a/packages/backend-core/src/events/publishers/index.ts b/packages/backend-core/src/events/publishers/index.ts index 6fe42c4bda..34e47b2990 100644 --- a/packages/backend-core/src/events/publishers/index.ts +++ b/packages/backend-core/src/events/publishers/index.ts @@ -1,21 +1,23 @@ -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" -export * as group from "./group" -export * as plugin from "./plugin" +export { default as account } from "./account" +export { default as app } from "./app" +export { default as auth } from "./auth" +export { default as automation } from "./automation" +export { default as datasource } from "./datasource" +export { default as email } from "./email" +export { default as license } from "./license" +export { default as layout } from "./layout" +export { default as org } from "./org" +export { default as query } from "./query" +export { default as role } from "./role" +export { default as screen } from "./screen" +export { default as rows } from "./rows" +export { default as table } from "./table" +export { default as serve } from "./serve" +export { default as user } from "./user" +export { default as view } from "./view" +export { default as installation } from "./installation" +export { default as backfill } from "./backfill" +export { default as group } from "./group" +export { default as plugin } from "./plugin" +export { default as backup } from "./backup" +export { default as environmentVariable } from "./environmentVariable" diff --git a/packages/backend-core/src/events/publishers/installation.ts b/packages/backend-core/src/events/publishers/installation.ts index ef27935210..f1ca75a0dd 100644 --- a/packages/backend-core/src/events/publishers/installation.ts +++ b/packages/backend-core/src/events/publishers/installation.ts @@ -1,14 +1,14 @@ import { publishEvent } from "../events" import { Event, VersionCheckedEvent, VersionChangeEvent } from "@budibase/types" -export async function versionChecked(version: string) { +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) { +async function upgraded(from: string, to: string) { const properties: VersionChangeEvent = { from, to, @@ -17,7 +17,7 @@ export async function upgraded(from: string, to: string) { await publishEvent(Event.INSTALLATION_VERSION_UPGRADED, properties) } -export async function downgraded(from: string, to: string) { +async function downgraded(from: string, to: string) { const properties: VersionChangeEvent = { from, to, @@ -25,7 +25,14 @@ export async function downgraded(from: string, to: string) { await publishEvent(Event.INSTALLATION_VERSION_DOWNGRADED, properties) } -export async function firstStartup() { +async function firstStartup() { const properties = {} await publishEvent(Event.INSTALLATION_FIRST_STARTUP, properties) } + +export default { + versionChecked, + upgraded, + downgraded, + firstStartup, +} diff --git a/packages/backend-core/src/events/publishers/layout.ts b/packages/backend-core/src/events/publishers/layout.ts index 1eede40143..6abb226049 100644 --- a/packages/backend-core/src/events/publishers/layout.ts +++ b/packages/backend-core/src/events/publishers/layout.ts @@ -6,16 +6,21 @@ import { LayoutDeletedEvent, } from "@budibase/types" -export async function created(layout: Layout, timestamp?: string | number) { +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) { +async function deleted(layoutId: string) { const properties: LayoutDeletedEvent = { layoutId, } await publishEvent(Event.LAYOUT_DELETED, properties) } + +export default { + created, + deleted, +} diff --git a/packages/backend-core/src/events/publishers/license.ts b/packages/backend-core/src/events/publishers/license.ts index 84472e408f..aff3286c87 100644 --- a/packages/backend-core/src/events/publishers/license.ts +++ b/packages/backend-core/src/events/publishers/license.ts @@ -13,7 +13,7 @@ import { LicensePaymentRecoveredEvent, } from "@budibase/types" -export async function tierChanged(account: Account, from: number, to: number) { +async function tierChanged(account: Account, from: number, to: number) { const properties: LicenseTierChangedEvent = { accountId: account.accountId, to, @@ -22,11 +22,7 @@ export async function tierChanged(account: Account, from: number, to: number) { await publishEvent(Event.LICENSE_TIER_CHANGED, properties) } -export async function planChanged( - account: Account, - from: PlanType, - to: PlanType -) { +async function planChanged(account: Account, from: PlanType, to: PlanType) { const properties: LicensePlanChangedEvent = { accountId: account.accountId, to, @@ -35,44 +31,55 @@ export async function planChanged( await publishEvent(Event.LICENSE_PLAN_CHANGED, properties) } -export async function activated(account: Account) { +async function activated(account: Account) { const properties: LicenseActivatedEvent = { accountId: account.accountId, } await publishEvent(Event.LICENSE_ACTIVATED, properties) } -export async function checkoutOpened(account: Account) { +async function checkoutOpened(account: Account) { const properties: LicenseCheckoutOpenedEvent = { accountId: account.accountId, } await publishEvent(Event.LICENSE_CHECKOUT_OPENED, properties) } -export async function checkoutSuccess(account: Account) { +async function checkoutSuccess(account: Account) { const properties: LicenseCheckoutSuccessEvent = { accountId: account.accountId, } await publishEvent(Event.LICENSE_CHECKOUT_SUCCESS, properties) } -export async function portalOpened(account: Account) { +async function portalOpened(account: Account) { const properties: LicensePortalOpenedEvent = { accountId: account.accountId, } await publishEvent(Event.LICENSE_PORTAL_OPENED, properties) } -export async function paymentFailed(account: Account) { +async function paymentFailed(account: Account) { const properties: LicensePaymentFailedEvent = { accountId: account.accountId, } await publishEvent(Event.LICENSE_PAYMENT_FAILED, properties) } -export async function paymentRecovered(account: Account) { +async function paymentRecovered(account: Account) { const properties: LicensePaymentRecoveredEvent = { accountId: account.accountId, } await publishEvent(Event.LICENSE_PAYMENT_RECOVERED, properties) } + +export default { + tierChanged, + planChanged, + activated, + checkoutOpened, + checkoutSuccess, + portalOpened, + paymentFailed, + paymentRecovered, +} diff --git a/packages/backend-core/src/events/publishers/org.ts b/packages/backend-core/src/events/publishers/org.ts index 4567357db8..684bb68dac 100644 --- a/packages/backend-core/src/events/publishers/org.ts +++ b/packages/backend-core/src/events/publishers/org.ts @@ -1,29 +1,37 @@ import { publishEvent } from "../events" import { Event } from "@budibase/types" -export async function nameUpdated(timestamp?: string | number) { +async function nameUpdated(timestamp?: string | number) { const properties = {} await publishEvent(Event.ORG_NAME_UPDATED, properties, timestamp) } -export async function logoUpdated(timestamp?: string | number) { +async function logoUpdated(timestamp?: string | number) { const properties = {} await publishEvent(Event.ORG_LOGO_UPDATED, properties, timestamp) } -export async function platformURLUpdated(timestamp?: string | number) { +async function platformURLUpdated(timestamp?: string | number) { const properties = {} await publishEvent(Event.ORG_PLATFORM_URL_UPDATED, properties, timestamp) } // TODO -export async function analyticsOptOut() { +async function analyticsOptOut() { const properties = {} await publishEvent(Event.ANALYTICS_OPT_OUT, properties) } -export async function analyticsOptIn() { +async function analyticsOptIn() { const properties = {} await publishEvent(Event.ANALYTICS_OPT_OUT, properties) } + +export default { + nameUpdated, + logoUpdated, + platformURLUpdated, + analyticsOptOut, + analyticsOptIn, +} diff --git a/packages/backend-core/src/events/publishers/plugin.ts b/packages/backend-core/src/events/publishers/plugin.ts index 4e4d87cf56..b5a686f95d 100644 --- a/packages/backend-core/src/events/publishers/plugin.ts +++ b/packages/backend-core/src/events/publishers/plugin.ts @@ -7,7 +7,7 @@ import { PluginInitEvent, } from "@budibase/types" -export async function init(plugin: Plugin) { +async function init(plugin: Plugin) { const properties: PluginInitEvent = { type: plugin.schema.type, name: plugin.name, @@ -17,7 +17,7 @@ export async function init(plugin: Plugin) { await publishEvent(Event.PLUGIN_INIT, properties) } -export async function imported(plugin: Plugin) { +async function imported(plugin: Plugin) { const properties: PluginImportedEvent = { pluginId: plugin._id as string, type: plugin.schema.type, @@ -29,7 +29,7 @@ export async function imported(plugin: Plugin) { await publishEvent(Event.PLUGIN_IMPORTED, properties) } -export async function deleted(plugin: Plugin) { +async function deleted(plugin: Plugin) { const properties: PluginDeletedEvent = { pluginId: plugin._id as string, type: plugin.schema.type, @@ -39,3 +39,9 @@ export async function deleted(plugin: Plugin) { } await publishEvent(Event.PLUGIN_DELETED, properties) } + +export default { + init, + imported, + deleted, +} diff --git a/packages/backend-core/src/events/publishers/query.ts b/packages/backend-core/src/events/publishers/query.ts index 1bcf561fd0..7d28129cf6 100644 --- a/packages/backend-core/src/events/publishers/query.ts +++ b/packages/backend-core/src/events/publishers/query.ts @@ -13,7 +13,7 @@ import { /* eslint-disable */ -export const created = async ( +const created = async ( datasource: Datasource, query: Query, timestamp?: string | number @@ -27,7 +27,7 @@ export const created = async ( await publishEvent(Event.QUERY_CREATED, properties, timestamp) } -export const updated = async (datasource: Datasource, query: Query) => { +const updated = async (datasource: Datasource, query: Query) => { const properties: QueryUpdatedEvent = { queryId: query._id as string, datasourceId: datasource._id as string, @@ -37,7 +37,7 @@ export const updated = async (datasource: Datasource, query: Query) => { await publishEvent(Event.QUERY_UPDATED, properties) } -export const deleted = async (datasource: Datasource, query: Query) => { +const deleted = async (datasource: Datasource, query: Query) => { const properties: QueryDeletedEvent = { queryId: query._id as string, datasourceId: datasource._id as string, @@ -47,7 +47,7 @@ export const deleted = async (datasource: Datasource, query: Query) => { await publishEvent(Event.QUERY_DELETED, properties) } -export const imported = async ( +const imported = async ( datasource: Datasource, importSource: any, count: any @@ -61,14 +61,14 @@ export const imported = async ( await publishEvent(Event.QUERY_IMPORT, properties) } -export const run = async (count: number, timestamp?: string | number) => { +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 previewed = async (datasource: Datasource, query: Query) => { const properties: QueryPreviewedEvent = { queryId: query._id, datasourceId: datasource._id as string, @@ -77,3 +77,12 @@ export const previewed = async (datasource: Datasource, query: Query) => { } await publishEvent(Event.QUERY_PREVIEWED, properties) } + +export default { + created, + updated, + deleted, + imported, + run, + previewed, +} diff --git a/packages/backend-core/src/events/publishers/role.ts b/packages/backend-core/src/events/publishers/role.ts index 99074d76a5..2fbdc8b76b 100644 --- a/packages/backend-core/src/events/publishers/role.ts +++ b/packages/backend-core/src/events/publishers/role.ts @@ -10,7 +10,7 @@ import { User, } from "@budibase/types" -export async function created(role: Role, timestamp?: string | number) { +async function created(role: Role, timestamp?: string | number) { const properties: RoleCreatedEvent = { roleId: role._id as string, permissionId: role.permissionId, @@ -19,7 +19,7 @@ export async function created(role: Role, timestamp?: string | number) { await publishEvent(Event.ROLE_CREATED, properties, timestamp) } -export async function updated(role: Role) { +async function updated(role: Role) { const properties: RoleUpdatedEvent = { roleId: role._id as string, permissionId: role.permissionId, @@ -28,7 +28,7 @@ export async function updated(role: Role) { await publishEvent(Event.ROLE_UPDATED, properties) } -export async function deleted(role: Role) { +async function deleted(role: Role) { const properties: RoleDeletedEvent = { roleId: role._id as string, permissionId: role.permissionId, @@ -37,7 +37,7 @@ export async function deleted(role: Role) { await publishEvent(Event.ROLE_DELETED, properties) } -export async function assigned(user: User, roleId: string, timestamp?: number) { +async function assigned(user: User, roleId: string, timestamp?: number) { const properties: RoleAssignedEvent = { userId: user._id as string, roleId, @@ -45,10 +45,18 @@ export async function assigned(user: User, roleId: string, timestamp?: number) { await publishEvent(Event.ROLE_ASSIGNED, properties, timestamp) } -export async function unassigned(user: User, roleId: string) { +async function unassigned(user: User, roleId: string) { const properties: RoleUnassignedEvent = { userId: user._id as string, roleId, } await publishEvent(Event.ROLE_UNASSIGNED, properties) } + +export default { + created, + updated, + deleted, + assigned, + unassigned, +} diff --git a/packages/backend-core/src/events/publishers/rows.ts b/packages/backend-core/src/events/publishers/rows.ts index 487e7ea074..9608613e89 100644 --- a/packages/backend-core/src/events/publishers/rows.ts +++ b/packages/backend-core/src/events/publishers/rows.ts @@ -3,28 +3,27 @@ import { Event, RowsImportedEvent, RowsCreatedEvent, - RowImportFormat, Table, } from "@budibase/types" /* eslint-disable */ -export const created = async (count: number, timestamp?: string | number) => { +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 imported = async (table: Table, count: number) => { const properties: RowsImportedEvent = { tableId: table._id as string, - format, count, } await publishEvent(Event.ROWS_IMPORTED, properties) } + +export default { + created, + imported, +} diff --git a/packages/backend-core/src/events/publishers/screen.ts b/packages/backend-core/src/events/publishers/screen.ts index 966bf72e52..27264b5847 100644 --- a/packages/backend-core/src/events/publishers/screen.ts +++ b/packages/backend-core/src/events/publishers/screen.ts @@ -6,7 +6,7 @@ import { ScreenDeletedEvent, } from "@budibase/types" -export async function created(screen: Screen, timestamp?: string | number) { +async function created(screen: Screen, timestamp?: string | number) { const properties: ScreenCreatedEvent = { layoutId: screen.layoutId, screenId: screen._id as string, @@ -15,7 +15,7 @@ export async function created(screen: Screen, timestamp?: string | number) { await publishEvent(Event.SCREEN_CREATED, properties, timestamp) } -export async function deleted(screen: Screen) { +async function deleted(screen: Screen) { const properties: ScreenDeletedEvent = { layoutId: screen.layoutId, screenId: screen._id as string, @@ -23,3 +23,8 @@ export async function deleted(screen: Screen) { } await publishEvent(Event.SCREEN_DELETED, properties) } + +export default { + created, + deleted, +} diff --git a/packages/backend-core/src/events/publishers/serve.ts b/packages/backend-core/src/events/publishers/serve.ts index 128e0b9b11..64e24e20a7 100644 --- a/packages/backend-core/src/events/publishers/serve.ts +++ b/packages/backend-core/src/events/publishers/serve.ts @@ -7,14 +7,14 @@ import { AppServedEvent, } from "@budibase/types" -export async function servedBuilder(timezone: string) { +async function servedBuilder(timezone: string) { const properties: BuilderServedEvent = { timezone, } await publishEvent(Event.SERVED_BUILDER, properties) } -export async function servedApp(app: App, timezone: string) { +async function servedApp(app: App, timezone: string) { const properties: AppServedEvent = { appVersion: app.version, timezone, @@ -22,7 +22,7 @@ export async function servedApp(app: App, timezone: string) { await publishEvent(Event.SERVED_APP, properties) } -export async function servedAppPreview(app: App, timezone: string) { +async function servedAppPreview(app: App, timezone: string) { const properties: AppPreviewServedEvent = { appId: app.appId, appVersion: app.version, @@ -30,3 +30,9 @@ export async function servedAppPreview(app: App, timezone: string) { } await publishEvent(Event.SERVED_APP_PREVIEW, properties) } + +export default { + servedBuilder, + servedApp, + servedAppPreview, +} diff --git a/packages/backend-core/src/events/publishers/table.ts b/packages/backend-core/src/events/publishers/table.ts index ba53163ca3..d50f4df0e1 100644 --- a/packages/backend-core/src/events/publishers/table.ts +++ b/packages/backend-core/src/events/publishers/table.ts @@ -2,7 +2,6 @@ import { publishEvent } from "../events" import { Event, TableExportFormat, - TableImportFormat, Table, TableCreatedEvent, TableUpdatedEvent, @@ -11,28 +10,28 @@ import { TableImportedEvent, } from "@budibase/types" -export async function created(table: Table, timestamp?: string | number) { +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) { +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) { +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) { +async function exported(table: Table, format: TableExportFormat) { const properties: TableExportedEvent = { tableId: table._id as string, format, @@ -40,10 +39,17 @@ export async function exported(table: Table, format: TableExportFormat) { await publishEvent(Event.TABLE_EXPORTED, properties) } -export async function imported(table: Table, format: TableImportFormat) { +async function imported(table: Table) { const properties: TableImportedEvent = { tableId: table._id as string, - format, } await publishEvent(Event.TABLE_IMPORTED, properties) } + +export default { + created, + updated, + deleted, + exported, + imported, +} diff --git a/packages/backend-core/src/events/publishers/user.ts b/packages/backend-core/src/events/publishers/user.ts index 2c83b267d6..1fe50149b5 100644 --- a/packages/backend-core/src/events/publishers/user.ts +++ b/packages/backend-core/src/events/publishers/user.ts @@ -13,32 +13,40 @@ import { UserPermissionAssignedEvent, UserPermissionRemovedEvent, UserUpdatedEvent, + UserOnboardingEvent, } from "@budibase/types" -export async function created(user: User, timestamp?: number) { +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) { +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) { +async function deleted(user: User) { const properties: UserDeletedEvent = { userId: user._id as string, } await publishEvent(Event.USER_DELETED, properties) } +export async function onboardingComplete(user: User) { + const properties: UserOnboardingEvent = { + userId: user._id as string, + } + await publishEvent(Event.USER_ONBOARDING_COMPLETE, properties) +} + // PERMISSIONS -export async function permissionAdminAssigned(user: User, timestamp?: number) { +async function permissionAdminAssigned(user: User, timestamp?: number) { const properties: UserPermissionAssignedEvent = { userId: user._id as string, } @@ -49,17 +57,14 @@ export async function permissionAdminAssigned(user: User, timestamp?: number) { ) } -export async function permissionAdminRemoved(user: User) { +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 -) { +async function permissionBuilderAssigned(user: User, timestamp?: number) { const properties: UserPermissionAssignedEvent = { userId: user._id as string, } @@ -70,7 +75,7 @@ export async function permissionBuilderAssigned( ) } -export async function permissionBuilderRemoved(user: User) { +async function permissionBuilderRemoved(user: User) { const properties: UserPermissionRemovedEvent = { userId: user._id as string, } @@ -79,12 +84,12 @@ export async function permissionBuilderRemoved(user: User) { // INVITE -export async function invited() { +async function invited() { const properties: UserInvitedEvent = {} await publishEvent(Event.USER_INVITED, properties) } -export async function inviteAccepted(user: User) { +async function inviteAccepted(user: User) { const properties: UserInviteAcceptedEvent = { userId: user._id as string, } @@ -93,30 +98,47 @@ export async function inviteAccepted(user: User) { // PASSWORD -export async function passwordForceReset(user: User) { +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) { +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) { +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) { +async function passwordReset(user: User) { const properties: UserPasswordResetEvent = { userId: user._id as string, } await publishEvent(Event.USER_PASSWORD_RESET, properties) } + +export default { + created, + updated, + deleted, + permissionAdminAssigned, + permissionAdminRemoved, + permissionBuilderAssigned, + permissionBuilderRemoved, + onboardingComplete, + invited, + inviteAccepted, + passwordForceReset, + passwordUpdated, + passwordResetRequested, + passwordReset, +} diff --git a/packages/backend-core/src/events/publishers/view.ts b/packages/backend-core/src/events/publishers/view.ts index 17947758d5..ccbf960b04 100644 --- a/packages/backend-core/src/events/publishers/view.ts +++ b/packages/backend-core/src/events/publishers/view.ts @@ -19,28 +19,28 @@ import { /* eslint-disable */ -export async function created(view: View, timestamp?: string | number) { +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) { +async function updated(view: View) { const properties: ViewUpdatedEvent = { tableId: view.tableId, } await publishEvent(Event.VIEW_UPDATED, properties) } -export async function deleted(view: View) { +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) { +async function exported(table: Table, format: TableExportFormat) { const properties: ViewExportedEvent = { tableId: table._id as string, format, @@ -48,31 +48,28 @@ export async function exported(table: Table, format: TableExportFormat) { await publishEvent(Event.VIEW_EXPORTED, properties) } -export async function filterCreated(view: View, timestamp?: string | number) { +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) { +async function filterUpdated(view: View) { const properties: ViewFilterUpdatedEvent = { tableId: view.tableId, } await publishEvent(Event.VIEW_FILTER_UPDATED, properties) } -export async function filterDeleted(view: View) { +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 -) { +async function calculationCreated(view: View, timestamp?: string | number) { const properties: ViewCalculationCreatedEvent = { tableId: view.tableId, calculation: view.calculation as ViewCalculation, @@ -80,7 +77,7 @@ export async function calculationCreated( await publishEvent(Event.VIEW_CALCULATION_CREATED, properties, timestamp) } -export async function calculationUpdated(view: View) { +async function calculationUpdated(view: View) { const properties: ViewCalculationUpdatedEvent = { tableId: view.tableId, calculation: view.calculation as ViewCalculation, @@ -88,10 +85,23 @@ export async function calculationUpdated(view: View) { await publishEvent(Event.VIEW_CALCULATION_UPDATED, properties) } -export async function calculationDeleted(existingView: View) { +async function calculationDeleted(existingView: View) { const properties: ViewCalculationDeletedEvent = { tableId: existingView.tableId, calculation: existingView.calculation as ViewCalculation, } await publishEvent(Event.VIEW_CALCULATION_DELETED, properties) } + +export default { + created, + updated, + deleted, + exported, + filterCreated, + filterUpdated, + filterDeleted, + calculationCreated, + calculationUpdated, + calculationDeleted, +} diff --git a/packages/backend-core/src/featureFlags/index.js b/packages/backend-core/src/featureFlags/index.js deleted file mode 100644 index 8a8162d0ba..0000000000 --- a/packages/backend-core/src/featureFlags/index.js +++ /dev/null @@ -1,60 +0,0 @@ -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() - const flags = exports.getTenantFeatureFlags(tenantId) - return flags.includes(featureFlag) -} - -exports.getTenantFeatureFlags = tenantId => { - const flags = [] - - if (TENANT_FEATURE_FLAGS) { - const globalFlags = TENANT_FEATURE_FLAGS["*"] - const tenantFlags = TENANT_FEATURE_FLAGS[tenantId] - - if (globalFlags) { - flags.push(...globalFlags) - } - if (tenantFlags) { - flags.push(...tenantFlags) - } - } - - return flags -} - -exports.TenantFeatureFlag = { - LICENSING: "LICENSING", - GOOGLE_SHEETS: "GOOGLE_SHEETS", - USER_GROUPS: "USER_GROUPS", -} diff --git a/packages/backend-core/src/featureFlags/index.ts b/packages/backend-core/src/featureFlags/index.ts new file mode 100644 index 0000000000..34ee3599a5 --- /dev/null +++ b/packages/backend-core/src/featureFlags/index.ts @@ -0,0 +1,77 @@ +import env from "../environment" +import * as tenancy from "../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 + */ +export function buildFeatureFlags() { + if (!env.TENANT_FEATURE_FLAGS) { + return + } + + const tenantFeatureFlags: Record = {} + + 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 +} + +export function isEnabled(featureFlag: string) { + const tenantId = tenancy.getTenantId() + const flags = getTenantFeatureFlags(tenantId) + return flags.includes(featureFlag) +} + +export function getTenantFeatureFlags(tenantId: string) { + let flags: string[] = [] + const envFlags = buildFeatureFlags() + if (envFlags) { + const globalFlags = envFlags["*"] + const tenantFlags = envFlags[tenantId] || [] + + // Explicitly exclude tenants from global features if required. + // Prefix the tenant flag with '!' + const tenantOverrides = tenantFlags.reduce( + (acc: string[], flag: string) => { + if (flag.startsWith("!")) { + let stripped = flag.substring(1) + acc.push(stripped) + } + return acc + }, + [] + ) + + if (globalFlags) { + flags.push(...globalFlags) + } + if (tenantFlags.length) { + flags.push(...tenantFlags) + } + + // Purge any tenant specific overrides + flags = flags.filter(flag => { + return tenantOverrides.indexOf(flag) == -1 && !flag.startsWith("!") + }) + } + + return flags +} + +export enum TenantFeatureFlag { + LICENSING = "LICENSING", + GOOGLE_SHEETS = "GOOGLE_SHEETS", + USER_GROUPS = "USER_GROUPS", + ONBOARDING_TOUR = "ONBOARDING_TOUR", +} diff --git a/packages/backend-core/src/featureFlags/tests/featureFlags.spec.ts b/packages/backend-core/src/featureFlags/tests/featureFlags.spec.ts new file mode 100644 index 0000000000..1b68959329 --- /dev/null +++ b/packages/backend-core/src/featureFlags/tests/featureFlags.spec.ts @@ -0,0 +1,85 @@ +import { + TenantFeatureFlag, + buildFeatureFlags, + getTenantFeatureFlags, +} from "../" +import env from "../../environment" + +const { ONBOARDING_TOUR, LICENSING, USER_GROUPS } = TenantFeatureFlag + +describe("featureFlags", () => { + beforeEach(() => { + env._set("TENANT_FEATURE_FLAGS", "") + }) + + it("Should return no flags when the TENANT_FEATURE_FLAG is empty", async () => { + let features = buildFeatureFlags() + expect(features).toBeUndefined() + }) + + it("Should generate a map of global and named tenant feature flags from the env value", async () => { + env._set( + "TENANT_FEATURE_FLAGS", + `*:${ONBOARDING_TOUR},tenant1:!${ONBOARDING_TOUR},tenant2:${USER_GROUPS},tenant1:${LICENSING}` + ) + + const parsedFlags: Record = { + "*": [ONBOARDING_TOUR], + tenant1: [`!${ONBOARDING_TOUR}`, LICENSING], + tenant2: [USER_GROUPS], + } + + let features = buildFeatureFlags() + + expect(features).toBeDefined() + expect(features).toEqual(parsedFlags) + }) + + it("Should add feature flag flag only to explicitly configured tenant", async () => { + env._set( + "TENANT_FEATURE_FLAGS", + `*:${LICENSING},*:${USER_GROUPS},tenant1:${ONBOARDING_TOUR}` + ) + + let tenant1Flags = getTenantFeatureFlags("tenant1") + let tenant2Flags = getTenantFeatureFlags("tenant2") + + expect(tenant1Flags).toBeDefined() + expect(tenant1Flags).toEqual([LICENSING, USER_GROUPS, ONBOARDING_TOUR]) + + expect(tenant2Flags).toBeDefined() + expect(tenant2Flags).toEqual([LICENSING, USER_GROUPS]) + }) +}) + +it("Should exclude tenant1 from global feature flag", async () => { + env._set( + "TENANT_FEATURE_FLAGS", + `*:${LICENSING},*:${ONBOARDING_TOUR},tenant1:!${ONBOARDING_TOUR}` + ) + + let tenant1Flags = getTenantFeatureFlags("tenant1") + let tenant2Flags = getTenantFeatureFlags("tenant2") + + expect(tenant1Flags).toBeDefined() + expect(tenant1Flags).toEqual([LICENSING]) + + expect(tenant2Flags).toBeDefined() + expect(tenant2Flags).toEqual([LICENSING, ONBOARDING_TOUR]) +}) + +it("Should explicitly add flags to configured tenants only", async () => { + env._set( + "TENANT_FEATURE_FLAGS", + `tenant1:${ONBOARDING_TOUR},tenant1:${LICENSING},tenant2:${LICENSING}` + ) + + let tenant1Flags = getTenantFeatureFlags("tenant1") + let tenant2Flags = getTenantFeatureFlags("tenant2") + + expect(tenant1Flags).toBeDefined() + expect(tenant1Flags).toEqual([ONBOARDING_TOUR, LICENSING]) + + expect(tenant2Flags).toBeDefined() + expect(tenant2Flags).toEqual([LICENSING]) +}) diff --git a/packages/worker/src/utilities/index.js b/packages/backend-core/src/helpers.ts similarity index 85% rename from packages/worker/src/utilities/index.js rename to packages/backend-core/src/helpers.ts index b402a82cf3..e1e065bd4e 100644 --- a/packages/worker/src/utilities/index.js +++ b/packages/backend-core/src/helpers.ts @@ -4,6 +4,6 @@ * @param {string} url The URL to test and remove any extra double slashes. * @return {string} The updated url. */ -exports.checkSlashesInUrl = url => { +export function checkSlashesInUrl(url: string) { return url.replace(/(https?:\/\/)|(\/)+/g, "$1$2") } diff --git a/packages/backend-core/src/index.ts b/packages/backend-core/src/index.ts index 83b23b479d..b38a53e9e4 100644 --- a/packages/backend-core/src/index.ts +++ b/packages/backend-core/src/index.ts @@ -1,67 +1,42 @@ -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 roles from "./security/roles" -import * as accounts from "./cloud/accounts" -import * as installation from "./installation" -import env from "./environment" -import tenancy from "./tenancy" -import featureFlags from "./featureFlags" -import * as sessions from "./security/sessions" -import deprovisioning from "./context/deprovision" -import auth from "./auth" -import constants from "./constants" -import * as dbConstants from "./db/constants" -import * as logging from "./logging" -import pino from "./pino" -import * as middleware from "./middleware" -import plugins from "./plugin" -import encryption from "./security/encryption" +export * as events from "./events" +export * as migrations from "./migrations" +export * as users from "./users" +export * as roles from "./security/roles" +export * as permissions from "./security/permissions" +export * as accounts from "./cloud/accounts" +export * as installation from "./installation" +export * as tenancy from "./tenancy" +export * as featureFlags from "./featureFlags" +export * as sessions from "./security/sessions" +export * as deprovisioning from "./context/deprovision" +export * as auth from "./auth" +export * as constants from "./constants" +export * as logging from "./logging" +export * as middleware from "./middleware" +export * as plugins from "./plugin" +export * as encryption from "./security/encryption" +export * as queue from "./queue" +export * as db from "./db" +export * as context from "./context" +export * as cache from "./cache" +export * as objectStore from "./objectStore" +export * as redis from "./redis" +export * as utils from "./utils" +export * as errors from "./errors" +export { default as env } from "./environment" -// 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" +// expose error classes directly +export * from "./errors" -const init = (opts: any = {}) => { +// expose constants directly +export * from "./constants" + +// expose inner locks from redis directly +import * as redis from "./redis" +export const locks = redis.redlock + +// expose package init function +import * as db from "./db" +export 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, - roles, - plugins, - ...pino, - ...errorClasses, - middleware, - encryption, -} - -export = core diff --git a/packages/backend-core/src/installation.ts b/packages/backend-core/src/installation.ts index da9b6c5b76..4e78a508a5 100644 --- a/packages/backend-core/src/installation.ts +++ b/packages/backend-core/src/installation.ts @@ -1,16 +1,16 @@ -import * as hashing from "./hashing" +import { newid } from "./utils" import * as events from "./events" -import { StaticDatabases } from "./db/constants" +import { StaticDatabases } from "./db" 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" +import { bustCache, withCache, TTL, CacheKey } from "./cache/generic" const pkg = require("../package.json") export const getInstall = async (): Promise => { - return withCache(CacheKeys.INSTALLATION, TTL.ONE_DAY, getInstallFromDB, { + return withCache(CacheKey.INSTALLATION, TTL.ONE_DAY, getInstallFromDB, { useTenancy: false, }) } @@ -28,7 +28,7 @@ const getInstallFromDB = async (): Promise => { if (e.status === 404) { install = { _id: StaticDatabases.PLATFORM_INFO.docs.install, - installId: hashing.newid(), + installId: newid(), version: pkg.version, } const resp = await platformDb.put(install) @@ -50,7 +50,7 @@ const updateVersion = async (version: string): Promise => { const install = await getInstall() install.version = version await platformDb.put(install) - await bustCache(CacheKeys.INSTALLATION) + await bustCache(CacheKey.INSTALLATION) } ) } catch (e: any) { diff --git a/packages/backend-core/src/logging.ts b/packages/backend-core/src/logging.ts index 3fc79a5fe7..8f2ae6e619 100644 --- a/packages/backend-core/src/logging.ts +++ b/packages/backend-core/src/logging.ts @@ -1,3 +1,9 @@ +import { Header } from "./constants" +import env from "./environment" +const correlator = require("correlation-id") +import { Options } from "pino-http" +import { IncomingMessage } from "http" + const NonErrors = ["AccountError"] function isSuppressed(e?: any) { @@ -29,8 +35,26 @@ export function logWarn(message: string) { console.warn(`bb-warn: ${message}`) } -export default { - logAlert, - logAlertWithInfo, - logWarn, +export function pinoSettings(): Options { + return { + prettyPrint: { + levelFirst: true, + }, + genReqId: correlator.getId, + level: env.LOG_LEVEL || "error", + autoLogging: { + ignore: (req: IncomingMessage) => !!req.url?.includes("/health"), + }, + } +} + +const setCorrelationHeader = (headers: any) => { + const correlationId = correlator.getId() + if (correlationId) { + headers[Header.CORRELATION_ID] = correlationId + } +} + +export const correlation = { + setHeader: setCorrelationHeader, } diff --git a/packages/backend-core/src/middleware/adminOnly.js b/packages/backend-core/src/middleware/adminOnly.ts similarity index 62% rename from packages/backend-core/src/middleware/adminOnly.js rename to packages/backend-core/src/middleware/adminOnly.ts index 4bfdf83848..dbe1e3a501 100644 --- a/packages/backend-core/src/middleware/adminOnly.js +++ b/packages/backend-core/src/middleware/adminOnly.ts @@ -1,4 +1,6 @@ -module.exports = async (ctx, next) => { +import { BBContext } from "@budibase/types" + +export default async (ctx: BBContext, next: any) => { if ( !ctx.internal && (!ctx.user || !ctx.user.admin || !ctx.user.admin.global) diff --git a/packages/backend-core/src/middleware/auditLog.js b/packages/backend-core/src/middleware/auditLog.js deleted file mode 100644 index c9063ae2e0..0000000000 --- a/packages/backend-core/src/middleware/auditLog.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = async (ctx, next) => { - // Placeholder for audit log middleware - return next() -} diff --git a/packages/backend-core/src/middleware/auditLog.ts b/packages/backend-core/src/middleware/auditLog.ts new file mode 100644 index 0000000000..9b76bb10b7 --- /dev/null +++ b/packages/backend-core/src/middleware/auditLog.ts @@ -0,0 +1,6 @@ +import { BBContext } from "@budibase/types" + +export default async (ctx: BBContext | any, next: any) => { + // Placeholder for audit log middleware + return next() +} diff --git a/packages/backend-core/src/middleware/authenticated.ts b/packages/backend-core/src/middleware/authenticated.ts index a3c6b67cde..3b5e9ae162 100644 --- a/packages/backend-core/src/middleware/authenticated.ts +++ b/packages/backend-core/src/middleware/authenticated.ts @@ -1,17 +1,18 @@ -import { Cookies, Headers } from "../constants" +import { Cookie, Header } from "../constants" import { getCookie, clearCookie, openJwt } from "../utils" import { getUser } from "../cache/user" import { getSession, updateSessionTTL } from "../security/sessions" import { buildMatcherRegex, matches } from "./matchers" -import { SEPARATOR } from "../db/constants" -import { ViewName } from "../db/utils" -import { queryGlobalView } from "../db/views" +import { SEPARATOR, queryGlobalView, ViewName } from "../db" import { getGlobalDB, doInTenant } from "../tenancy" import { decrypt } from "../security/encryption" -const identity = require("../context/identity") -const env = require("../environment") +import * as identity from "../context/identity" +import env from "../environment" +import { BBContext, EndpointMatcher } from "@budibase/types" -const ONE_MINUTE = env.SESSION_UPDATE_PERIOD || 60 * 1000 +const ONE_MINUTE = env.SESSION_UPDATE_PERIOD + ? parseInt(env.SESSION_UPDATE_PERIOD) + : 60 * 1000 interface FinaliseOpts { authenticated?: boolean @@ -42,13 +43,13 @@ async function checkApiKey(apiKey: string, populateUser?: Function) { return doInTenant(tenantId, async () => { const db = getGlobalDB() // api key is encrypted in the database - const userId = await queryGlobalView( + const userId = (await queryGlobalView( ViewName.BY_API_KEY, { key: apiKey, }, db - ) + )) as string if (userId) { return { valid: true, @@ -65,16 +66,16 @@ async function checkApiKey(apiKey: string, populateUser?: Function) { * The tenancy modules should not be used here and it should be assumed that the tenancy context * has not yet been populated. */ -export = ( - noAuthPatterns = [], - opts: { publicAllowed: boolean; populateUser?: Function } = { +export default function ( + noAuthPatterns: EndpointMatcher[] = [], + opts: { publicAllowed?: boolean; populateUser?: Function } = { publicAllowed: false, } -) => { +) { const noAuthOptions = noAuthPatterns ? buildMatcherRegex(noAuthPatterns) : [] - return async (ctx: any, next: any) => { + return async (ctx: BBContext | any, next: any) => { let publicEndpoint = false - const version = ctx.request.headers[Headers.API_VER] + const version = ctx.request.headers[Header.API_VER] // the path is not authenticated const found = matches(ctx, noAuthOptions) if (found) { @@ -82,10 +83,10 @@ export = ( } try { // check the actual user is authenticated first, try header or cookie - const headerToken = ctx.request.headers[Headers.TOKEN] - const authCookie = getCookie(ctx, Cookies.Auth) || openJwt(headerToken) - const apiKey = ctx.request.headers[Headers.API_KEY] - const tenantId = ctx.request.headers[Headers.TENANT_ID] + const headerToken = ctx.request.headers[Header.TOKEN] + const authCookie = getCookie(ctx, Cookie.Auth) || openJwt(headerToken) + const apiKey = ctx.request.headers[Header.API_KEY] + const tenantId = ctx.request.headers[Header.TENANT_ID] let authenticated = false, user = null, internal = false @@ -116,7 +117,7 @@ export = ( authenticated = false console.error("Auth Error", err?.message || err) // remove the cookie as the user does not exist anymore - clearCookie(ctx, Cookies.Auth) + clearCookie(ctx, Cookie.Auth) } } // this is an internal request, no user made it @@ -140,7 +141,7 @@ export = ( delete user.password } // be explicit - if (authenticated !== true) { + if (!authenticated) { authenticated = false } // isAuthenticated is a function, so use a variable to be able to check authed state @@ -152,9 +153,10 @@ export = ( return next() } } catch (err: any) { + console.error("Auth Error", err?.message || err) // invalid token, clear the cookie if (err && err.name === "JsonWebTokenError") { - clearCookie(ctx, Cookies.Auth) + clearCookie(ctx, Cookie.Auth) } // allow configuring for public access if ((opts && opts.publicAllowed) || publicEndpoint) { diff --git a/packages/backend-core/src/middleware/builderOnly.js b/packages/backend-core/src/middleware/builderOnly.ts similarity index 63% rename from packages/backend-core/src/middleware/builderOnly.js rename to packages/backend-core/src/middleware/builderOnly.ts index 2128626db4..a00fd63a22 100644 --- a/packages/backend-core/src/middleware/builderOnly.js +++ b/packages/backend-core/src/middleware/builderOnly.ts @@ -1,4 +1,6 @@ -module.exports = async (ctx, next) => { +import { BBContext } from "@budibase/types" + +export default async (ctx: BBContext, next: any) => { if ( !ctx.internal && (!ctx.user || !ctx.user.builder || !ctx.user.builder.global) diff --git a/packages/backend-core/src/middleware/builderOrAdmin.js b/packages/backend-core/src/middleware/builderOrAdmin.ts similarity index 70% rename from packages/backend-core/src/middleware/builderOrAdmin.js rename to packages/backend-core/src/middleware/builderOrAdmin.ts index 6440766298..26bb3a1bda 100644 --- a/packages/backend-core/src/middleware/builderOrAdmin.js +++ b/packages/backend-core/src/middleware/builderOrAdmin.ts @@ -1,4 +1,6 @@ -module.exports = async (ctx, next) => { +import { BBContext } from "@budibase/types" + +export default async (ctx: BBContext, next: any) => { if ( !ctx.internal && (!ctx.user || !ctx.user.builder || !ctx.user.builder.global) && diff --git a/packages/backend-core/src/middleware/csrf.js b/packages/backend-core/src/middleware/csrf.ts similarity index 84% rename from packages/backend-core/src/middleware/csrf.js rename to packages/backend-core/src/middleware/csrf.ts index 12bd9473e6..cced4d5f7d 100644 --- a/packages/backend-core/src/middleware/csrf.js +++ b/packages/backend-core/src/middleware/csrf.ts @@ -1,5 +1,6 @@ -const { Headers } = require("../constants") -const { buildMatcherRegex, matches } = require("./matchers") +import { Header } from "../constants" +import { buildMatcherRegex, matches } from "./matchers" +import { BBContext, EndpointMatcher } from "@budibase/types" /** * GET, HEAD and OPTIONS methods are considered safe operations @@ -31,9 +32,11 @@ const INCLUDED_CONTENT_TYPES = [ * https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#synchronizer-token-pattern * */ -module.exports = (opts = { noCsrfPatterns: [] }) => { +export default function ( + opts: { noCsrfPatterns: EndpointMatcher[] } = { noCsrfPatterns: [] } +) { const noCsrfOptions = buildMatcherRegex(opts.noCsrfPatterns) - return async (ctx, next) => { + return async (ctx: BBContext | any, next: any) => { // don't apply for excluded paths const found = matches(ctx, noCsrfOptions) if (found) { @@ -62,13 +65,13 @@ module.exports = (opts = { noCsrfPatterns: [] }) => { // apply csrf when there is a token in the session (new logins) // in future there should be a hard requirement that the token is present - const userToken = ctx.user.csrfToken + const userToken = ctx.user?.csrfToken if (!userToken) { return next() } // reject if no token in request or mismatch - const requestToken = ctx.get(Headers.CSRF_TOKEN) + const requestToken = ctx.get(Header.CSRF_TOKEN) if (!requestToken || requestToken !== userToken) { ctx.throw(403, "Invalid CSRF token") } diff --git a/packages/backend-core/src/middleware/index.ts b/packages/backend-core/src/middleware/index.ts index 998c231b3d..4986cde64b 100644 --- a/packages/backend-core/src/middleware/index.ts +++ b/packages/backend-core/src/middleware/index.ts @@ -1,38 +1,19 @@ -const jwt = require("./passport/jwt") -const local = require("./passport/local") -const google = require("./passport/google") -const oidc = require("./passport/oidc") -const { authError, ssoCallbackUrl } = require("./passport/utils") -const authenticated = require("./authenticated") -const auditLog = require("./auditLog") -const tenancy = require("./tenancy") -const internalApi = require("./internalApi") -const datasourceGoogle = require("./passport/datasource/google") -const csrf = require("./csrf") -const adminOnly = require("./adminOnly") -const builderOrAdmin = require("./builderOrAdmin") -const builderOnly = require("./builderOnly") -const joiValidator = require("./joi-validator") - -const pkg = { - google, - oidc, - jwt, - local, - authenticated, - auditLog, - tenancy, - authError, - internalApi, - ssoCallbackUrl, - datasource: { - google: datasourceGoogle, - }, - csrf, - adminOnly, - builderOnly, - builderOrAdmin, - joiValidator, +export * as jwt from "./passport/jwt" +export * as local from "./passport/local" +export * as google from "./passport/google" +export * as oidc from "./passport/oidc" +import * as datasourceGoogle from "./passport/datasource/google" +export const datasource = { + google: datasourceGoogle, } - -export = pkg +export { authError, ssoCallbackUrl } from "./passport/utils" +export { default as authenticated } from "./authenticated" +export { default as auditLog } from "./auditLog" +export { default as tenancy } from "./tenancy" +export { default as internalApi } from "./internalApi" +export { default as csrf } from "./csrf" +export { default as adminOnly } from "./adminOnly" +export { default as builderOrAdmin } from "./builderOrAdmin" +export { default as builderOnly } from "./builderOnly" +export { default as logging } from "./logging" +export * as joiValidator from "./joi-validator" diff --git a/packages/backend-core/src/middleware/internalApi.js b/packages/backend-core/src/middleware/internalApi.js deleted file mode 100644 index 275d559a9e..0000000000 --- a/packages/backend-core/src/middleware/internalApi.js +++ /dev/null @@ -1,14 +0,0 @@ -const env = require("../environment") -const { Headers } = require("../constants") - -/** - * API Key only endpoint. - */ -module.exports = async (ctx, next) => { - const apiKey = ctx.request.headers[Headers.API_KEY] - if (apiKey !== env.INTERNAL_API_KEY) { - ctx.throw(403, "Unauthorized") - } - - return next() -} diff --git a/packages/backend-core/src/middleware/internalApi.ts b/packages/backend-core/src/middleware/internalApi.ts new file mode 100644 index 0000000000..fff761928b --- /dev/null +++ b/packages/backend-core/src/middleware/internalApi.ts @@ -0,0 +1,15 @@ +import env from "../environment" +import { Header } from "../constants" +import { BBContext } from "@budibase/types" + +/** + * API Key only endpoint. + */ +export default async (ctx: BBContext, next: any) => { + const apiKey = ctx.request.headers[Header.API_KEY] + if (apiKey !== env.INTERNAL_API_KEY) { + ctx.throw(403, "Unauthorized") + } + + return next() +} diff --git a/packages/server/src/middleware/joi-validator.js b/packages/backend-core/src/middleware/joi-validator.ts similarity index 50% rename from packages/server/src/middleware/joi-validator.js rename to packages/backend-core/src/middleware/joi-validator.ts index 6812dbdd54..fcc8316886 100644 --- a/packages/server/src/middleware/joi-validator.js +++ b/packages/backend-core/src/middleware/joi-validator.ts @@ -1,21 +1,27 @@ -const Joi = require("joi") +import Joi, { ObjectSchema } from "joi" +import { BBContext } from "@budibase/types" -function validate(schema, property) { +function validate( + schema: Joi.ObjectSchema | Joi.ArraySchema, + property: string +) { // Return a Koa middleware function - return (ctx, next) => { + return (ctx: BBContext, next: any) => { if (!schema) { return next() } let params = null + // @ts-ignore + let reqProp = ctx.request?.[property] if (ctx[property] != null) { params = ctx[property] - } else if (ctx.request[property] != null) { - params = ctx.request[property] + } else if (reqProp != null) { + params = reqProp } // not all schemas have the append property e.g. array schemas - if (schema.append) { - schema = schema.append({ + if ((schema as Joi.ObjectSchema).append) { + schema = (schema as Joi.ObjectSchema).append({ createdAt: Joi.any().optional(), updatedAt: Joi.any().optional(), }) @@ -30,10 +36,10 @@ function validate(schema, property) { } } -module.exports.body = schema => { +export function body(schema: Joi.ObjectSchema | Joi.ArraySchema) { return validate(schema, "body") } -module.exports.params = schema => { +export function params(schema: Joi.ObjectSchema | Joi.ArraySchema) { return validate(schema, "params") } diff --git a/packages/backend-core/src/middleware/logging.ts b/packages/backend-core/src/middleware/logging.ts new file mode 100644 index 0000000000..d1f2d6566b --- /dev/null +++ b/packages/backend-core/src/middleware/logging.ts @@ -0,0 +1,88 @@ +const correlator = require("correlation-id") +import { Header } from "../constants" +import { v4 as uuid } from "uuid" +import * as context from "../context" + +const debug = console.warn +const trace = console.trace +const log = console.log +const info = console.info +const warn = console.warn +const error = console.error + +const getTenantId = () => { + let tenantId + try { + tenantId = context.getTenantId() + } catch (e: any) { + // do nothing + } + return tenantId +} + +const getAppId = () => { + let appId + try { + appId = context.getAppId() + } catch (e) { + // do nothing + } + return appId +} + +const getIdentityId = () => { + let identityId + try { + const identity = context.getIdentity() + identityId = identity?._id + } catch (e) { + // do nothing + } + return identityId +} + +const print = (fn: any, data: any[]) => { + let message = "" + + const correlationId = correlator.getId() + if (correlationId) { + message = message + `[correlationId=${correlator.getId()}]` + } + + const tenantId = getTenantId() + if (tenantId) { + message = message + ` [tenantId=${tenantId}]` + } + + const appId = getAppId() + if (appId) { + message = message + ` [appId=${appId}]` + } + + const identityId = getIdentityId() + if (identityId) { + message = message + ` [identityId=${identityId}]` + } + + fn(message, data) +} + +const logging = (ctx: any, next: any) => { + // use the provided correlation id header if present + let correlationId = ctx.headers[Header.CORRELATION_ID] + if (!correlationId) { + correlationId = uuid() + } + + return correlator.withId(correlationId, () => { + console.debug = data => print(debug, data) + console.trace = data => print(trace, data) + console.log = data => print(log, data) + console.info = data => print(info, data) + console.warn = data => print(warn, data) + console.error = data => print(error, data) + return next() + }) +} + +export default logging diff --git a/packages/backend-core/src/middleware/matchers.js b/packages/backend-core/src/middleware/matchers.ts similarity index 62% rename from packages/backend-core/src/middleware/matchers.js rename to packages/backend-core/src/middleware/matchers.ts index 3d5065c069..efbdec2dbe 100644 --- a/packages/backend-core/src/middleware/matchers.js +++ b/packages/backend-core/src/middleware/matchers.ts @@ -1,27 +1,34 @@ +import { BBContext, EndpointMatcher, RegexMatcher } from "@budibase/types" + const PARAM_REGEX = /\/:(.*?)(\/.*)?$/g -exports.buildMatcherRegex = patterns => { +export const buildMatcherRegex = ( + patterns: EndpointMatcher[] +): RegexMatcher[] => { if (!patterns) { return [] } return patterns.map(pattern => { - const isObj = typeof pattern === "object" && pattern.route - const method = isObj ? pattern.method : "GET" + let route = pattern.route + const method = pattern.method const strict = pattern.strict ? pattern.strict : false - let route = isObj ? pattern.route : pattern + // if there is a param in the route + // use a wildcard pattern const matches = route.match(PARAM_REGEX) if (matches) { for (let match of matches) { - const pattern = "/.*" + (match.endsWith("/") ? "/" : "") + const suffix = match.endsWith("/") ? "/" : "" + const pattern = "/.*" + suffix route = route.replace(match, pattern) } } + return { regex: new RegExp(route), method, strict, route } }) } -exports.matches = (ctx, options) => { +export const matches = (ctx: BBContext, options: RegexMatcher[]) => { return options.find(({ regex, method, strict, route }) => { let urlMatch if (strict) { diff --git a/packages/backend-core/src/middleware/passport/datasource/google.js b/packages/backend-core/src/middleware/passport/datasource/google.ts similarity index 67% rename from packages/backend-core/src/middleware/passport/datasource/google.js rename to packages/backend-core/src/middleware/passport/datasource/google.ts index 8f2022c2d7..65620d7aa3 100644 --- a/packages/backend-core/src/middleware/passport/datasource/google.js +++ b/packages/backend-core/src/middleware/passport/datasource/google.ts @@ -1,17 +1,21 @@ -const google = require("../google") +import * as google from "../google" +import { Cookie, Config } from "../../../constants" +import { clearCookie, getCookie } from "../../../utils" +import { getScopedConfig, getPlatformUrl, doWithDB } from "../../../db" +import environment from "../../../environment" +import { getGlobalDB } from "../../../tenancy" +import { BBContext, Database, SSOProfile } from "@budibase/types" const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy -const { Cookies, Configs } = require("../../../constants") -const { clearCookie, getCookie } = require("../../../utils") -const { getScopedConfig, getPlatformUrl } = require("../../../db/utils") -const { doWithDB } = require("../../../db") -const environment = require("../../../environment") -const { getGlobalDB } = require("../../../tenancy") + +type Passport = { + authenticate: any +} async function fetchGoogleCreds() { // try and get the config from the tenant const db = getGlobalDB() const googleConfig = await getScopedConfig(db, { - type: Configs.GOOGLE, + type: Config.GOOGLE, }) // or fall back to env variables return ( @@ -22,7 +26,11 @@ async function fetchGoogleCreds() { ) } -async function preAuth(passport, ctx, next) { +export async function preAuth( + passport: Passport, + ctx: BBContext, + next: Function +) { // get the relevant config const googleConfig = await fetchGoogleCreds() const platformUrl = await getPlatformUrl({ tenantAware: false }) @@ -41,13 +49,17 @@ async function preAuth(passport, ctx, next) { })(ctx, next) } -async function postAuth(passport, ctx, next) { +export async function postAuth( + passport: Passport, + ctx: BBContext, + next: Function +) { // get the relevant config const config = await fetchGoogleCreds() const platformUrl = await getPlatformUrl({ tenantAware: false }) let callbackUrl = `${platformUrl}/api/global/auth/datasource/google/callback` - const authStateCookie = getCookie(ctx, Cookies.DatasourceAuth) + const authStateCookie = getCookie(ctx, Cookie.DatasourceAuth) return passport.authenticate( new GoogleStrategy( @@ -56,15 +68,20 @@ async function postAuth(passport, ctx, next) { clientSecret: config.clientSecret, callbackURL: callbackUrl, }, - (accessToken, refreshToken, profile, done) => { - clearCookie(ctx, Cookies.DatasourceAuth) + ( + accessToken: string, + refreshToken: string, + profile: SSOProfile, + done: Function + ) => { + clearCookie(ctx, Cookie.DatasourceAuth) done(null, { accessToken, refreshToken }) } ), { successRedirect: "/", failureRedirect: "/error" }, - async (err, tokens) => { + async (err: any, tokens: string[]) => { // update the DB for the datasource with all the user info - await doWithDB(authStateCookie.appId, async db => { + await doWithDB(authStateCookie.appId, async (db: Database) => { const datasource = await db.get(authStateCookie.datasourceId) if (!datasource.config) { datasource.config = {} @@ -78,6 +95,3 @@ async function postAuth(passport, ctx, next) { } )(ctx, next) } - -exports.preAuth = preAuth -exports.postAuth = postAuth diff --git a/packages/backend-core/src/middleware/passport/google.js b/packages/backend-core/src/middleware/passport/google.ts similarity index 62% rename from packages/backend-core/src/middleware/passport/google.js rename to packages/backend-core/src/middleware/passport/google.ts index 7419974cd7..dd3dc8b86d 100644 --- a/packages/backend-core/src/middleware/passport/google.js +++ b/packages/backend-core/src/middleware/passport/google.ts @@ -1,10 +1,15 @@ +import { ssoCallbackUrl } from "./utils" +import { authenticateThirdParty, SaveUserFunction } from "./third-party-common" +import { ConfigType, GoogleConfig, Database, SSOProfile } from "@budibase/types" const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy -const { ssoCallbackUrl } = require("./utils") -const { authenticateThirdParty } = require("./third-party-common") -const { Configs } = require("../../../constants") -const buildVerifyFn = saveUserFn => { - return (accessToken, refreshToken, profile, done) => { +export function buildVerifyFn(saveUserFn?: SaveUserFunction) { + return ( + accessToken: string, + refreshToken: string, + profile: SSOProfile, + done: Function + ) => { const thirdPartyUser = { provider: profile.provider, // should always be 'google' providerType: "google", @@ -31,7 +36,11 @@ const buildVerifyFn = saveUserFn => { * 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, saveUserFn) { +export async function strategyFactory( + config: GoogleConfig["config"], + callbackUrl: string, + saveUserFn?: SaveUserFunction +) { try { const { clientID, clientSecret } = config @@ -50,18 +59,15 @@ exports.strategyFactory = async function (config, callbackUrl, saveUserFn) { }, verify ) - } catch (err) { + } catch (err: any) { console.error(err) - throw new Error( - `Error constructing google authentication strategy: ${err}`, - err - ) + throw new Error(`Error constructing google authentication strategy: ${err}`) } } -exports.getCallbackUrl = async function (db, config) { - return ssoCallbackUrl(db, config, Configs.GOOGLE) +export async function getCallbackUrl( + db: Database, + config: { callbackURL?: string } +) { + return ssoCallbackUrl(db, config, ConfigType.GOOGLE) } - -// expose for testing -exports.buildVerifyFn = buildVerifyFn diff --git a/packages/backend-core/src/middleware/passport/jwt.js b/packages/backend-core/src/middleware/passport/jwt.js deleted file mode 100644 index 690c2ac8a1..0000000000 --- a/packages/backend-core/src/middleware/passport/jwt.js +++ /dev/null @@ -1,18 +0,0 @@ -const { Cookies } = require("../../constants") -const env = require("../../environment") -const { authError } = require("./utils") - -exports.options = { - secretOrKey: env.JWT_SECRET, - jwtFromRequest: function (ctx) { - return ctx.cookies.get(Cookies.Auth) - }, -} - -exports.authenticate = async function (jwt, done) { - try { - return done(null, jwt) - } catch (err) { - return authError(done, "JWT invalid", err) - } -} diff --git a/packages/backend-core/src/middleware/passport/jwt.ts b/packages/backend-core/src/middleware/passport/jwt.ts new file mode 100644 index 0000000000..95dc8f2656 --- /dev/null +++ b/packages/backend-core/src/middleware/passport/jwt.ts @@ -0,0 +1,19 @@ +import { Cookie } from "../../constants" +import env from "../../environment" +import { authError } from "./utils" +import { BBContext } from "@budibase/types" + +export const options = { + secretOrKey: env.JWT_SECRET, + jwtFromRequest: function (ctx: BBContext) { + return ctx.cookies.get(Cookie.Auth) + }, +} + +export async function authenticate(jwt: Function, done: Function) { + try { + return done(null, jwt) + } catch (err) { + return authError(done, "JWT invalid", err) + } +} diff --git a/packages/backend-core/src/middleware/passport/local.js b/packages/backend-core/src/middleware/passport/local.ts similarity index 73% rename from packages/backend-core/src/middleware/passport/local.js rename to packages/backend-core/src/middleware/passport/local.ts index b955d29102..8b85d3734c 100644 --- a/packages/backend-core/src/middleware/passport/local.js +++ b/packages/backend-core/src/middleware/passport/local.ts @@ -1,18 +1,18 @@ +import { UserStatus } from "../../constants" +import { compare, newid } from "../../utils" +import env from "../../environment" +import * as users from "../../users" +import { authError } from "./utils" +import { createASession } from "../../security/sessions" +import { getTenantId } from "../../tenancy" +import { BBContext } from "@budibase/types" const jwt = require("jsonwebtoken") -const { UserStatus } = require("../../constants") -const { compare } = require("../../hashing") -const env = require("../../environment") -const users = require("../../users") -const { authError } = require("./utils") -const { newid } = require("../../hashing") -const { createASession } = require("../../security/sessions") -const { getTenantId } = require("../../tenancy") const INVALID_ERR = "Invalid credentials" const SSO_NO_PASSWORD = "SSO user does not have a password set" const EXPIRED = "This account has expired. Please reset your password" -exports.options = { +export const options = { passReqToCallback: true, } @@ -24,7 +24,12 @@ exports.options = { * @param {*} done callback from passport to return user information and errors * @returns The authenticated user, or errors if they occur */ -exports.authenticate = async function (ctx, email, password, done) { +export async function authenticate( + ctx: BBContext, + email: string, + password: string, + done: Function +) { if (!email) return authError(done, "Email Required") if (!password) return authError(done, "Password Required") @@ -56,9 +61,9 @@ exports.authenticate = async function (ctx, email, password, done) { const sessionId = newid() const tenantId = getTenantId() - await createASession(dbUser._id, { sessionId, tenantId }) + await createASession(dbUser._id!, { sessionId, tenantId }) - dbUser.token = jwt.sign( + const token = jwt.sign( { userId: dbUser._id, sessionId, @@ -69,7 +74,10 @@ exports.authenticate = async function (ctx, email, password, done) { // Remove users password in payload delete dbUser.password - return done(null, dbUser) + return done(null, { + ...dbUser, + token, + }) } else { return authError(done, INVALID_ERR) } diff --git a/packages/backend-core/src/middleware/passport/oidc.js b/packages/backend-core/src/middleware/passport/oidc.ts similarity index 72% rename from packages/backend-core/src/middleware/passport/oidc.js rename to packages/backend-core/src/middleware/passport/oidc.ts index 20dbd4669b..7caa177cf0 100644 --- a/packages/backend-core/src/middleware/passport/oidc.js +++ b/packages/backend-core/src/middleware/passport/oidc.ts @@ -1,10 +1,22 @@ -const fetch = require("node-fetch") +import fetch from "node-fetch" +import { authenticateThirdParty, SaveUserFunction } from "./third-party-common" +import { ssoCallbackUrl } from "./utils" +import { + ConfigType, + OIDCInnerCfg, + Database, + SSOProfile, + ThirdPartyUser, + OIDCConfiguration, +} from "@budibase/types" const OIDCStrategy = require("@techpass/passport-openidconnect").Strategy -const { authenticateThirdParty } = require("./third-party-common") -const { ssoCallbackUrl } = require("./utils") -const { Configs } = require("../../../constants") -const buildVerifyFn = saveUserFn => { +type JwtClaims = { + preferred_username: string + email: string +} + +export function buildVerifyFn(saveUserFn?: SaveUserFunction) { /** * @param {*} issuer The identity provider base URL * @param {*} sub The user ID @@ -17,17 +29,17 @@ const buildVerifyFn = saveUserFn => { * @param {*} done The passport callback: err, user, info */ return async ( - issuer, - sub, - profile, - jwtClaims, - accessToken, - refreshToken, - idToken, - params, - done + issuer: string, + sub: string, + profile: SSOProfile, + jwtClaims: JwtClaims, + accessToken: string, + refreshToken: string, + idToken: string, + params: any, + done: Function ) => { - const thirdPartyUser = { + const thirdPartyUser: ThirdPartyUser = { // store the issuer info to enable sync in future provider: issuer, providerType: "oidc", @@ -53,7 +65,7 @@ const buildVerifyFn = saveUserFn => { * @param {*} profile The structured profile created by passport using the user info endpoint * @param {*} jwtClaims The claims returned in the id token */ -function getEmail(profile, jwtClaims) { +function getEmail(profile: SSOProfile, jwtClaims: JwtClaims) { // profile not guaranteed to contain email e.g. github connected azure ad account if (profile._json.email) { return profile._json.email @@ -77,7 +89,7 @@ function getEmail(profile, jwtClaims) { ) } -function validEmail(value) { +function validEmail(value: string) { return ( value && !!value.match( @@ -91,19 +103,25 @@ 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, saveUserFn) { +export async function strategyFactory( + config: OIDCConfiguration, + saveUserFn?: SaveUserFunction +) { try { const verify = buildVerifyFn(saveUserFn) const strategy = new OIDCStrategy(config, verify) strategy.name = "oidc" return strategy - } catch (err) { + } catch (err: any) { console.error(err) - throw new Error("Error constructing OIDC authentication strategy", err) + throw new Error(`Error constructing OIDC authentication strategy - ${err}`) } } -exports.fetchStrategyConfig = async function (enrichedConfig, callbackUrl) { +export async function fetchStrategyConfig( + enrichedConfig: OIDCInnerCfg, + callbackUrl?: string +): Promise { try { const { clientID, clientSecret, configUrl } = enrichedConfig @@ -135,13 +153,15 @@ exports.fetchStrategyConfig = async function (enrichedConfig, callbackUrl) { } } catch (err) { console.error(err) - throw new Error("Error constructing OIDC authentication configuration", err) + throw new Error( + `Error constructing OIDC authentication configuration - ${err}` + ) } } -exports.getCallbackUrl = async function (db, config) { - return ssoCallbackUrl(db, config, Configs.OIDC) +export async function getCallbackUrl( + db: Database, + config: { callbackURL?: string } +) { + return ssoCallbackUrl(db, config, ConfigType.OIDC) } - -// expose for testing -exports.buildVerifyFn = buildVerifyFn 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 c00ab2ea7d..4c8aa94ddf 100644 --- a/packages/backend-core/src/middleware/passport/tests/oidc.spec.js +++ b/packages/backend-core/src/middleware/passport/tests/oidc.spec.js @@ -1,7 +1,6 @@ // Mock data - +const mockFetch = require("node-fetch") const { data } = require("./utilities/mock-data") - const issuer = "mockIssuer" const sub = "mockSub" const profile = { @@ -39,8 +38,6 @@ describe("oidc", () => { const mockStrategy = require("@techpass/passport-openidconnect").Strategy // mock the request to retrieve the oidc configuration - jest.mock("node-fetch") - const mockFetch = require("node-fetch") mockFetch.mockReturnValue({ ok: true, json: () => oidcConfigUrlResponse 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 41a253b328..d377d602f1 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,10 +1,10 @@ -require("../../../../tests/utilities/TestConfiguration") +require("../../../../tests") const { authenticateThirdParty } = require("../third-party-common") const { data } = require("./utilities/mock-data") const { DEFAULT_TENANT_ID } = require("../../../constants") const { generateGlobalUserID } = require("../../../db/utils") -const { newid } = require("../../../hashing") +const { newid } = require("../../../utils") const { doWithGlobalDB, doInTenant } = require("../../../tenancy") const done = jest.fn() diff --git a/packages/backend-core/src/middleware/passport/third-party-common.js b/packages/backend-core/src/middleware/passport/third-party-common.ts similarity index 70% rename from packages/backend-core/src/middleware/passport/third-party-common.js rename to packages/backend-core/src/middleware/passport/third-party-common.ts index 1c5891fce7..9d7b93f370 100644 --- a/packages/backend-core/src/middleware/passport/third-party-common.js +++ b/packages/backend-core/src/middleware/passport/third-party-common.ts @@ -1,21 +1,33 @@ -const env = require("../../environment") +import env from "../../environment" +import { generateGlobalUserID } from "../../db" +import { authError } from "./utils" +import { newid } from "../../utils" +import { createASession } from "../../security/sessions" +import * as users from "../../users" +import { getGlobalDB, getTenantId } from "../../tenancy" +import fetch from "node-fetch" +import { ThirdPartyUser } from "@budibase/types" const jwt = require("jsonwebtoken") -const { generateGlobalUserID } = require("../../db/utils") -const { authError } = require("./utils") -const { newid } = require("../../hashing") -const { createASession } = require("../../security/sessions") -const users = require("../../users") -const { getGlobalDB, getTenantId } = require("../../tenancy") -const fetch = require("node-fetch") + +type SaveUserOpts = { + requirePassword?: boolean + hashPassword?: boolean + currentUserId?: string +} + +export type SaveUserFunction = ( + user: ThirdPartyUser, + opts: SaveUserOpts +) => Promise /** * Common authentication logic for third parties. e.g. OAuth, OIDC. */ -exports.authenticateThirdParty = async function ( - thirdPartyUser, - requireLocalAccount = true, - done, - saveUserFn +export async function authenticateThirdParty( + thirdPartyUser: ThirdPartyUser, + requireLocalAccount: boolean = true, + done: Function, + saveUserFn?: SaveUserFunction ) { if (!saveUserFn) { throw new Error("Save user function must be provided") @@ -39,7 +51,7 @@ exports.authenticateThirdParty = async function ( // try to load by id try { dbUser = await db.get(userId) - } catch (err) { + } catch (err: any) { // abort when not 404 error if (!err.status || err.status !== 404) { return authError( @@ -80,9 +92,9 @@ exports.authenticateThirdParty = async function ( // create or sync the user try { - await saveUserFn(dbUser, false, false) - } catch (err) { - return authError(done, err) + await saveUserFn(dbUser, { hashPassword: false, requirePassword: false }) + } catch (err: any) { + return authError(done, "Error saving user", err) } // now that we're sure user exists, load them from the db @@ -104,13 +116,16 @@ exports.authenticateThirdParty = async function ( return done(null, dbUser) } -async function syncProfilePicture(user, thirdPartyUser) { - const pictureUrl = thirdPartyUser.profile._json.picture +async function syncProfilePicture( + user: ThirdPartyUser, + thirdPartyUser: ThirdPartyUser +) { + const pictureUrl = thirdPartyUser.profile?._json.picture if (pictureUrl) { const response = await fetch(pictureUrl) if (response.status === 200) { - const type = response.headers.get("content-type") + const type = response.headers.get("content-type") as string if (type.startsWith("image/")) { user.pictureUrl = pictureUrl } @@ -123,7 +138,7 @@ async function syncProfilePicture(user, thirdPartyUser) { /** * @returns a user that has been sync'd with third party information */ -async function syncUser(user, thirdPartyUser) { +async function syncUser(user: ThirdPartyUser, thirdPartyUser: ThirdPartyUser) { // provider user.provider = thirdPartyUser.provider user.providerType = thirdPartyUser.providerType diff --git a/packages/backend-core/src/middleware/passport/utils.js b/packages/backend-core/src/middleware/passport/utils.ts similarity index 64% rename from packages/backend-core/src/middleware/passport/utils.js rename to packages/backend-core/src/middleware/passport/utils.ts index 217130cd6d..3d79aada28 100644 --- a/packages/backend-core/src/middleware/passport/utils.js +++ b/packages/backend-core/src/middleware/passport/utils.ts @@ -1,6 +1,6 @@ -const { isMultiTenant, getTenantId } = require("../../tenancy") -const { getScopedConfig } = require("../../db/utils") -const { Configs } = require("../../constants") +import { isMultiTenant, getTenantId } from "../../tenancy" +import { getScopedConfig } from "../../db" +import { ConfigType, Database, Config } from "@budibase/types" /** * Utility to handle authentication errors. @@ -10,7 +10,7 @@ const { Configs } = require("../../constants") * @param {*} err (Optional) error that will be logged */ -exports.authError = function (done, message, err = null) { +export function authError(done: Function, message: string, err?: any) { return done( err, null, // never return a user @@ -18,13 +18,17 @@ exports.authError = function (done, message, err = null) { ) } -exports.ssoCallbackUrl = async (db, config, type) => { +export async function ssoCallbackUrl( + db: Database, + config?: { callbackURL?: string }, + type?: ConfigType +) { // incase there is a callback URL from before if (config && config.callbackURL) { return config.callbackURL } const publicConfig = await getScopedConfig(db, { - type: Configs.SETTINGS, + type: ConfigType.SETTINGS, }) let callbackUrl = `/api/global/auth` diff --git a/packages/backend-core/src/middleware/tenancy.js b/packages/backend-core/src/middleware/tenancy.js deleted file mode 100644 index 8083322b29..0000000000 --- a/packages/backend-core/src/middleware/tenancy.js +++ /dev/null @@ -1,52 +0,0 @@ -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, - noTenancyPatterns, - opts = { noTenancyRequired: false } -) => { - const allowQsOptions = buildMatcherRegex(allowQueryStringPatterns) - const noTenancyOptions = buildMatcherRegex(noTenancyPatterns) - - return async function (ctx, next) { - const allowNoTenant = - opts.noTenancyRequired || !!matches(ctx, noTenancyOptions) - const allowQs = !!matches(ctx, allowQsOptions) - const tenantId = getTenantID(ctx, { allowQs, allowNoTenant }) - return doInTenant(tenantId, next) - } -} diff --git a/packages/backend-core/src/middleware/tenancy.ts b/packages/backend-core/src/middleware/tenancy.ts new file mode 100644 index 0000000000..a09c463045 --- /dev/null +++ b/packages/backend-core/src/middleware/tenancy.ts @@ -0,0 +1,35 @@ +import { doInTenant, getTenantIDFromCtx } from "../tenancy" +import { buildMatcherRegex, matches } from "./matchers" +import { Header } from "../constants" +import { + BBContext, + EndpointMatcher, + GetTenantIdOptions, + TenantResolutionStrategy, +} from "@budibase/types" + +export default function ( + allowQueryStringPatterns: EndpointMatcher[], + noTenancyPatterns: EndpointMatcher[], + opts: { noTenancyRequired?: boolean } = { noTenancyRequired: false } +) { + const allowQsOptions = buildMatcherRegex(allowQueryStringPatterns) + const noTenancyOptions = buildMatcherRegex(noTenancyPatterns) + + return async function (ctx: BBContext | any, next: any) { + const allowNoTenant = + opts.noTenancyRequired || !!matches(ctx, noTenancyOptions) + const tenantOpts: GetTenantIdOptions = { + allowNoTenant, + } + + const allowQs = !!matches(ctx, allowQsOptions) + if (!allowQs) { + tenantOpts.excludeStrategies = [TenantResolutionStrategy.QUERY] + } + + const tenantId = getTenantIDFromCtx(ctx, tenantOpts) + ctx.set(Header.TENANT_ID, tenantId as string) + return doInTenant(tenantId, next) + } +} diff --git a/packages/backend-core/src/middleware/tests/matchers.spec.ts b/packages/backend-core/src/middleware/tests/matchers.spec.ts new file mode 100644 index 0000000000..c39bbb6dd3 --- /dev/null +++ b/packages/backend-core/src/middleware/tests/matchers.spec.ts @@ -0,0 +1,134 @@ +import * as matchers from "../matchers" +import { structures } from "../../../tests" + +describe("matchers", () => { + it("matches by path and method", () => { + const pattern = [ + { + route: "/api/tests", + method: "POST", + }, + ] + const ctx = structures.koa.newContext() + ctx.request.url = "/api/tests" + ctx.request.method = "POST" + + const built = matchers.buildMatcherRegex(pattern) + + expect(!!matchers.matches(ctx, built)).toBe(true) + }) + + it("wildcards path", () => { + const pattern = [ + { + route: "/api/tests", + method: "POST", + }, + ] + const ctx = structures.koa.newContext() + ctx.request.url = "/api/tests/id/something/else" + ctx.request.method = "POST" + + const built = matchers.buildMatcherRegex(pattern) + + expect(!!matchers.matches(ctx, built)).toBe(true) + }) + + it("doesn't wildcard path with strict", () => { + const pattern = [ + { + route: "/api/tests", + method: "POST", + strict: true, + }, + ] + const ctx = structures.koa.newContext() + ctx.request.url = "/api/tests/id/something/else" + ctx.request.method = "POST" + + const built = matchers.buildMatcherRegex(pattern) + + expect(!!matchers.matches(ctx, built)).toBe(false) + }) + + it("matches with param", () => { + const pattern = [ + { + route: "/api/tests/:testId", + method: "GET", + }, + ] + const ctx = structures.koa.newContext() + ctx.request.url = "/api/tests/id" + ctx.request.method = "GET" + + const built = matchers.buildMatcherRegex(pattern) + + expect(!!matchers.matches(ctx, built)).toBe(true) + }) + + // TODO: Support the below behaviour + // Strict does not work when a param is present + // it("matches with param with strict", () => { + // const pattern = [{ + // route: "/api/tests/:testId", + // method: "GET", + // strict: true + // }] + // const ctx = structures.koa.newContext() + // ctx.request.url = "/api/tests/id" + // ctx.request.method = "GET" + // + // const built = matchers.buildMatcherRegex(pattern) + // + // expect(!!matchers.matches(ctx, built)).toBe(true) + // }) + + it("doesn't match by path", () => { + const pattern = [ + { + route: "/api/tests", + method: "POST", + }, + ] + const ctx = structures.koa.newContext() + ctx.request.url = "/api/unknown" + ctx.request.method = "POST" + + const built = matchers.buildMatcherRegex(pattern) + + expect(!!matchers.matches(ctx, built)).toBe(false) + }) + + it("doesn't match by method", () => { + const pattern = [ + { + route: "/api/tests", + method: "POST", + }, + ] + const ctx = structures.koa.newContext() + ctx.request.url = "/api/tests" + ctx.request.method = "GET" + + const built = matchers.buildMatcherRegex(pattern) + + expect(!!matchers.matches(ctx, built)).toBe(false) + }) + + it("matches by path and wildcard method", () => { + const pattern = [ + { + route: "/api/tests", + method: "ALL", + }, + ] + const ctx = structures.koa.newContext() + ctx.request.url = "/api/tests" + ctx.request.method = "GET" + + const built = matchers.buildMatcherRegex(pattern) + + expect(!!matchers.matches(ctx, built)).toBe(true) + }) +}) diff --git a/packages/backend-core/src/migrations/definitions.ts b/packages/backend-core/src/migrations/definitions.ts index 946fc3f364..0dd57fe639 100644 --- a/packages/backend-core/src/migrations/definitions.ts +++ b/packages/backend-core/src/migrations/definitions.ts @@ -11,7 +11,7 @@ export const DEFINITIONS: MigrationDefinition[] = [ }, { type: MigrationType.GLOBAL, - name: MigrationName.QUOTAS_1, + name: MigrationName.SYNC_QUOTAS, }, { type: MigrationType.APP, @@ -21,6 +21,10 @@ export const DEFINITIONS: MigrationDefinition[] = [ type: MigrationType.APP, name: MigrationName.EVENT_APP_BACKFILL, }, + { + type: MigrationType.APP, + name: MigrationName.TABLE_SETTINGS_LINKS_TO_ACTIONS, + }, { type: MigrationType.GLOBAL, name: MigrationName.EVENT_GLOBAL_BACKFILL, @@ -33,8 +37,4 @@ export const DEFINITIONS: MigrationDefinition[] = [ type: MigrationType.GLOBAL, name: MigrationName.GLOBAL_INFO_SYNC_USERS, }, - { - type: MigrationType.GLOBAL, - name: MigrationName.PLUGIN_COUNT, - }, ] diff --git a/packages/backend-core/src/migrations/migrations.ts b/packages/backend-core/src/migrations/migrations.ts index 90a12acec2..55b8ab1938 100644 --- a/packages/backend-core/src/migrations/migrations.ts +++ b/packages/backend-core/src/migrations/migrations.ts @@ -1,10 +1,13 @@ import { DEFAULT_TENANT_ID } from "../constants" -import { doWithDB } from "../db" -import { DocumentType, StaticDatabases } from "../db/constants" -import { getAllApps } from "../db/utils" +import { + DocumentType, + StaticDatabases, + getAllApps, + getGlobalDBName, + doWithDB, +} from "../db" import environment from "../environment" import { doInTenant, getTenantIds, getTenantId } from "../tenancy" -import { getGlobalDBName } from "../db/tenancy" import * as context from "../context" import { DEFINITIONS } from "." import { @@ -12,6 +15,7 @@ import { MigrationOptions, MigrationType, MigrationNoOpOptions, + App, } from "@budibase/types" export const getMigrationsDoc = async (db: any) => { @@ -41,7 +45,7 @@ export const runMigration = async ( options: MigrationOptions = {} ) => { const migrationType = migration.type - let tenantId: string + let tenantId: string | undefined if (migrationType !== MigrationType.INSTALLATION) { tenantId = getTenantId() } @@ -55,14 +59,17 @@ export const runMigration = async ( } // get the db to store the migration in - let dbNames + let dbNames: string[] if (migrationType === MigrationType.GLOBAL) { dbNames = [getGlobalDBName()] } else if (migrationType === MigrationType.APP) { if (options.noOp) { + if (!options.noOp.appId) { + throw new Error("appId is required for noOp app migration") + } dbNames = [options.noOp.appId] } else { - const apps = await getAllApps(migration.appOpts) + const apps = (await getAllApps(migration.appOpts)) as App[] dbNames = apps.map(app => app.appId) } } else if (migrationType === MigrationType.INSTALLATION) { @@ -81,7 +88,7 @@ export const runMigration = async ( await doWithDB(dbName, async (db: any) => { try { - const doc = await exports.getMigrationsDoc(db) + const doc = await getMigrationsDoc(db) // the migration has already been run if (doc[migrationName]) { diff --git a/packages/backend-core/src/migrations/tests/index.spec.js b/packages/backend-core/src/migrations/tests/index.spec.js index c5ec143143..b7d2e14ea5 100644 --- a/packages/backend-core/src/migrations/tests/index.spec.js +++ b/packages/backend-core/src/migrations/tests/index.spec.js @@ -1,9 +1,9 @@ -require("../../../tests/utilities/TestConfiguration") +require("../../../tests") const { runMigrations, getMigrationsDoc } = require("../index") -const { dangerousGetDB } = require("../../db") +const { getDB } = require("../../db") const { StaticDatabases, -} = require("../../db/utils") +} = require("../../constants") let db @@ -18,7 +18,7 @@ describe("migrations", () => { }] beforeEach(() => { - db = dangerousGetDB(StaticDatabases.GLOBAL.name) + db = getDB(StaticDatabases.GLOBAL.name) }) afterEach(async () => { diff --git a/packages/backend-core/src/newid.ts b/packages/backend-core/src/newid.ts new file mode 100644 index 0000000000..5676c23f48 --- /dev/null +++ b/packages/backend-core/src/newid.ts @@ -0,0 +1,5 @@ +import { v4 } from "uuid" + +export function newid() { + return v4().replace(/-/g, "") +} diff --git a/packages/backend-core/src/objectStore/buckets/app.ts b/packages/backend-core/src/objectStore/buckets/app.ts new file mode 100644 index 0000000000..9951058d6a --- /dev/null +++ b/packages/backend-core/src/objectStore/buckets/app.ts @@ -0,0 +1,40 @@ +import env from "../../environment" +import * as objectStore from "../objectStore" +import * as cloudfront from "../cloudfront" + +/** + * In production the client library is stored in the object store, however in development + * we use the symlinked version produced by lerna, located in node modules. We link to this + * via a specific endpoint (under /api/assets/client). + * @param {string} appId In production we need the appId to look up the correct bucket, as the + * version of the client lib may differ between apps. + * @param {string} version The version to retrieve. + * @return {string} The URL to be inserted into appPackage response or server rendered + * app index file. + */ +export const clientLibraryUrl = (appId: string, version: string) => { + if (env.isProd()) { + let file = `${objectStore.sanitizeKey(appId)}/budibase-client.js` + if (env.CLOUDFRONT_CDN) { + // append app version to bust the cache + if (version) { + file += `?v=${version}` + } + // don't need to use presigned for client with cloudfront + // file is public + return cloudfront.getUrl(file) + } else { + return objectStore.getPresignedUrl(env.APPS_BUCKET_NAME, file) + } + } else { + return `/api/assets/client` + } +} + +export const getAppFileUrl = (s3Key: string) => { + if (env.CLOUDFRONT_CDN) { + return cloudfront.getPresignedUrl(s3Key) + } else { + return objectStore.getPresignedUrl(env.APPS_BUCKET_NAME, s3Key) + } +} diff --git a/packages/backend-core/src/objectStore/buckets/global.ts b/packages/backend-core/src/objectStore/buckets/global.ts new file mode 100644 index 0000000000..8bf883b11e --- /dev/null +++ b/packages/backend-core/src/objectStore/buckets/global.ts @@ -0,0 +1,29 @@ +import env from "../../environment" +import * as tenancy from "../../tenancy" +import * as objectStore from "../objectStore" +import * as cloudfront from "../cloudfront" + +// URLs + +export const getGlobalFileUrl = (type: string, name: string, etag?: string) => { + let file = getGlobalFileS3Key(type, name) + if (env.CLOUDFRONT_CDN) { + if (etag) { + file = `${file}?etag=${etag}` + } + return cloudfront.getPresignedUrl(file) + } else { + return objectStore.getPresignedUrl(env.GLOBAL_BUCKET_NAME, file) + } +} + +// KEYS + +export const getGlobalFileS3Key = (type: string, name: string) => { + let file = `${type}/${name}` + if (env.MULTI_TENANCY) { + const tenantId = tenancy.getTenantId() + file = `${tenantId}/${file}` + } + return file +} diff --git a/packages/backend-core/src/objectStore/buckets/index.ts b/packages/backend-core/src/objectStore/buckets/index.ts new file mode 100644 index 0000000000..8398242ee5 --- /dev/null +++ b/packages/backend-core/src/objectStore/buckets/index.ts @@ -0,0 +1,3 @@ +export * from "./app" +export * from "./global" +export * from "./plugins" diff --git a/packages/backend-core/src/objectStore/buckets/plugins.ts b/packages/backend-core/src/objectStore/buckets/plugins.ts new file mode 100644 index 0000000000..cd3bf77e87 --- /dev/null +++ b/packages/backend-core/src/objectStore/buckets/plugins.ts @@ -0,0 +1,71 @@ +import env from "../../environment" +import * as objectStore from "../objectStore" +import * as tenancy from "../../tenancy" +import * as cloudfront from "../cloudfront" +import { Plugin } from "@budibase/types" + +// URLS + +export const enrichPluginURLs = (plugins: Plugin[]) => { + if (!plugins || !plugins.length) { + return [] + } + return plugins.map(plugin => { + const jsUrl = getPluginJSUrl(plugin) + const iconUrl = getPluginIconUrl(plugin) + return { ...plugin, jsUrl, iconUrl } + }) +} + +const getPluginJSUrl = (plugin: Plugin) => { + const s3Key = getPluginJSKey(plugin) + return getPluginUrl(s3Key) +} + +const getPluginIconUrl = (plugin: Plugin): string | undefined => { + const s3Key = getPluginIconKey(plugin) + if (!s3Key) { + return + } + return getPluginUrl(s3Key) +} + +const getPluginUrl = (s3Key: string) => { + if (env.CLOUDFRONT_CDN) { + return cloudfront.getPresignedUrl(s3Key) + } else { + return objectStore.getPresignedUrl(env.PLUGIN_BUCKET_NAME, s3Key) + } +} + +// S3 KEYS + +export const getPluginJSKey = (plugin: Plugin) => { + return getPluginS3Key(plugin, "plugin.min.js") +} + +export const getPluginIconKey = (plugin: Plugin) => { + // stored iconUrl is deprecated - hardcode to icon.svg in this case + const iconFileName = plugin.iconUrl ? "icon.svg" : plugin.iconFileName + if (!iconFileName) { + return + } + return getPluginS3Key(plugin, iconFileName) +} + +const getPluginS3Key = (plugin: Plugin, fileName: string) => { + const s3Key = getPluginS3Dir(plugin.name) + return `${s3Key}/${fileName}` +} + +export const getPluginS3Dir = (pluginName: string) => { + let s3Key = `${pluginName}` + if (env.MULTI_TENANCY) { + const tenantId = tenancy.getTenantId() + s3Key = `${tenantId}/${s3Key}` + } + if (env.CLOUDFRONT_CDN) { + s3Key = `plugins/${s3Key}` + } + return s3Key +} diff --git a/packages/backend-core/src/objectStore/buckets/tests/app.spec.ts b/packages/backend-core/src/objectStore/buckets/tests/app.spec.ts new file mode 100644 index 0000000000..0375e97cbc --- /dev/null +++ b/packages/backend-core/src/objectStore/buckets/tests/app.spec.ts @@ -0,0 +1,171 @@ +import * as app from "../app" +import { getAppFileUrl } from "../app" +import { testEnv } from "../../../../tests" + +describe("app", () => { + beforeEach(() => { + testEnv.nodeJest() + }) + + describe("clientLibraryUrl", () => { + function getClientUrl() { + return app.clientLibraryUrl("app_123/budibase-client.js", "2.0.0") + } + + describe("single tenant", () => { + beforeAll(() => { + testEnv.singleTenant() + }) + + it("gets url in dev", () => { + testEnv.nodeDev() + const url = getClientUrl() + expect(url).toBe("/api/assets/client") + }) + + it("gets url with embedded minio", () => { + testEnv.withMinio() + const url = getClientUrl() + expect(url).toBe( + "/files/signed/prod-budi-app-assets/app_123/budibase-client.js/budibase-client.js" + ) + }) + + it("gets url with custom S3", () => { + testEnv.withS3() + const url = getClientUrl() + expect(url).toBe( + "http://s3.example.com/prod-budi-app-assets/app_123/budibase-client.js/budibase-client.js" + ) + }) + + it("gets url with cloudfront + s3", () => { + testEnv.withCloudfront() + const url = getClientUrl() + expect(url).toBe( + "http://cf.example.com/app_123/budibase-client.js/budibase-client.js?v=2.0.0" + ) + }) + }) + + describe("multi tenant", () => { + beforeAll(() => { + testEnv.multiTenant() + }) + + it("gets url in dev", async () => { + testEnv.nodeDev() + await testEnv.withTenant(tenantId => { + const url = getClientUrl() + expect(url).toBe("/api/assets/client") + }) + }) + + it("gets url with embedded minio", async () => { + await testEnv.withTenant(tenantId => { + testEnv.withMinio() + const url = getClientUrl() + expect(url).toBe( + "/files/signed/prod-budi-app-assets/app_123/budibase-client.js/budibase-client.js" + ) + }) + }) + + it("gets url with custom S3", async () => { + await testEnv.withTenant(tenantId => { + testEnv.withS3() + const url = getClientUrl() + expect(url).toBe( + "http://s3.example.com/prod-budi-app-assets/app_123/budibase-client.js/budibase-client.js" + ) + }) + }) + + it("gets url with cloudfront + s3", async () => { + await testEnv.withTenant(tenantId => { + testEnv.withCloudfront() + const url = getClientUrl() + expect(url).toBe( + "http://cf.example.com/app_123/budibase-client.js/budibase-client.js?v=2.0.0" + ) + }) + }) + }) + }) + + describe("getAppFileUrl", () => { + function getAppFileUrl() { + return app.getAppFileUrl("app_123/attachments/image.jpeg") + } + + describe("single tenant", () => { + beforeAll(() => { + testEnv.multiTenant() + }) + + it("gets url with embedded minio", () => { + testEnv.withMinio() + const url = getAppFileUrl() + expect(url).toBe( + "/files/signed/prod-budi-app-assets/app_123/attachments/image.jpeg" + ) + }) + + it("gets url with custom S3", () => { + testEnv.withS3() + const url = getAppFileUrl() + expect(url).toBe( + "http://s3.example.com/prod-budi-app-assets/app_123/attachments/image.jpeg" + ) + }) + + it("gets url with cloudfront + s3", () => { + testEnv.withCloudfront() + const url = getAppFileUrl() + // omit rest of signed params + expect( + url.includes("http://cf.example.com/app_123/attachments/image.jpeg?") + ).toBe(true) + }) + }) + + describe("multi tenant", () => { + beforeAll(() => { + testEnv.multiTenant() + }) + + it("gets url with embedded minio", async () => { + testEnv.withMinio() + await testEnv.withTenant(tenantId => { + const url = getAppFileUrl() + expect(url).toBe( + "/files/signed/prod-budi-app-assets/app_123/attachments/image.jpeg" + ) + }) + }) + + it("gets url with custom S3", async () => { + testEnv.withS3() + await testEnv.withTenant(tenantId => { + const url = getAppFileUrl() + expect(url).toBe( + "http://s3.example.com/prod-budi-app-assets/app_123/attachments/image.jpeg" + ) + }) + }) + + it("gets url with cloudfront + s3", async () => { + testEnv.withCloudfront() + await testEnv.withTenant(tenantId => { + const url = getAppFileUrl() + // omit rest of signed params + expect( + url.includes( + "http://cf.example.com/app_123/attachments/image.jpeg?" + ) + ).toBe(true) + }) + }) + }) + }) +}) diff --git a/packages/backend-core/src/objectStore/buckets/tests/global.spec.ts b/packages/backend-core/src/objectStore/buckets/tests/global.spec.ts new file mode 100644 index 0000000000..b495812356 --- /dev/null +++ b/packages/backend-core/src/objectStore/buckets/tests/global.spec.ts @@ -0,0 +1,74 @@ +import * as global from "../global" +import { testEnv } from "../../../../tests" + +describe("global", () => { + describe("getGlobalFileUrl", () => { + function getGlobalFileUrl() { + return global.getGlobalFileUrl("settings", "logoUrl", "etag") + } + + describe("single tenant", () => { + beforeAll(() => { + testEnv.singleTenant() + }) + + it("gets url with embedded minio", () => { + testEnv.withMinio() + const url = getGlobalFileUrl() + expect(url).toBe("/files/signed/global/settings/logoUrl") + }) + + it("gets url with custom S3", () => { + testEnv.withS3() + const url = getGlobalFileUrl() + expect(url).toBe("http://s3.example.com/global/settings/logoUrl") + }) + + it("gets url with cloudfront + s3", () => { + testEnv.withCloudfront() + const url = getGlobalFileUrl() + // omit rest of signed params + expect( + url.includes("http://cf.example.com/settings/logoUrl?etag=etag&") + ).toBe(true) + }) + }) + + describe("multi tenant", () => { + beforeAll(() => { + testEnv.multiTenant() + }) + + it("gets url with embedded minio", async () => { + testEnv.withMinio() + await testEnv.withTenant(tenantId => { + const url = getGlobalFileUrl() + expect(url).toBe(`/files/signed/global/${tenantId}/settings/logoUrl`) + }) + }) + + it("gets url with custom S3", async () => { + testEnv.withS3() + await testEnv.withTenant(tenantId => { + const url = getGlobalFileUrl() + expect(url).toBe( + `http://s3.example.com/global/${tenantId}/settings/logoUrl` + ) + }) + }) + + it("gets url with cloudfront + s3", async () => { + testEnv.withCloudfront() + await testEnv.withTenant(tenantId => { + const url = getGlobalFileUrl() + // omit rest of signed params + expect( + url.includes( + `http://cf.example.com/${tenantId}/settings/logoUrl?etag=etag&` + ) + ).toBe(true) + }) + }) + }) + }) +}) diff --git a/packages/backend-core/src/objectStore/buckets/tests/plugins.spec.ts b/packages/backend-core/src/objectStore/buckets/tests/plugins.spec.ts new file mode 100644 index 0000000000..affb8d8318 --- /dev/null +++ b/packages/backend-core/src/objectStore/buckets/tests/plugins.spec.ts @@ -0,0 +1,110 @@ +import * as plugins from "../plugins" +import { structures, testEnv } from "../../../../tests" + +describe("plugins", () => { + describe("enrichPluginURLs", () => { + const plugin = structures.plugins.plugin() + + function getEnrichedPluginUrls() { + const enriched = plugins.enrichPluginURLs([plugin])[0] + return { + jsUrl: enriched.jsUrl!, + iconUrl: enriched.iconUrl!, + } + } + + describe("single tenant", () => { + beforeAll(() => { + testEnv.singleTenant() + }) + + it("gets url with embedded minio", () => { + testEnv.withMinio() + const urls = getEnrichedPluginUrls() + expect(urls.jsUrl).toBe( + `/files/signed/plugins/${plugin.name}/plugin.min.js` + ) + expect(urls.iconUrl).toBe( + `/files/signed/plugins/${plugin.name}/icon.svg` + ) + }) + + it("gets url with custom S3", () => { + testEnv.withS3() + const urls = getEnrichedPluginUrls() + expect(urls.jsUrl).toBe( + `http://s3.example.com/plugins/${plugin.name}/plugin.min.js` + ) + expect(urls.iconUrl).toBe( + `http://s3.example.com/plugins/${plugin.name}/icon.svg` + ) + }) + + it("gets url with cloudfront + s3", () => { + testEnv.withCloudfront() + const urls = getEnrichedPluginUrls() + // omit rest of signed params + expect( + urls.jsUrl.includes( + `http://cf.example.com/plugins/${plugin.name}/plugin.min.js?` + ) + ).toBe(true) + expect( + urls.iconUrl.includes( + `http://cf.example.com/plugins/${plugin.name}/icon.svg?` + ) + ).toBe(true) + }) + }) + + describe("multi tenant", () => { + beforeAll(() => { + testEnv.multiTenant() + }) + + it("gets url with embedded minio", async () => { + testEnv.withMinio() + await testEnv.withTenant(tenantId => { + const urls = getEnrichedPluginUrls() + expect(urls.jsUrl).toBe( + `/files/signed/plugins/${tenantId}/${plugin.name}/plugin.min.js` + ) + expect(urls.iconUrl).toBe( + `/files/signed/plugins/${tenantId}/${plugin.name}/icon.svg` + ) + }) + }) + + it("gets url with custom S3", async () => { + testEnv.withS3() + await testEnv.withTenant(tenantId => { + const urls = getEnrichedPluginUrls() + expect(urls.jsUrl).toBe( + `http://s3.example.com/plugins/${tenantId}/${plugin.name}/plugin.min.js` + ) + expect(urls.iconUrl).toBe( + `http://s3.example.com/plugins/${tenantId}/${plugin.name}/icon.svg` + ) + }) + }) + + it("gets url with cloudfront + s3", async () => { + testEnv.withCloudfront() + await testEnv.withTenant(tenantId => { + const urls = getEnrichedPluginUrls() + // omit rest of signed params + expect( + urls.jsUrl.includes( + `http://cf.example.com/plugins/${tenantId}/${plugin.name}/plugin.min.js?` + ) + ).toBe(true) + expect( + urls.iconUrl.includes( + `http://cf.example.com/plugins/${tenantId}/${plugin.name}/icon.svg?` + ) + ).toBe(true) + }) + }) + }) + }) +}) diff --git a/packages/backend-core/src/objectStore/cloudfront.ts b/packages/backend-core/src/objectStore/cloudfront.ts new file mode 100644 index 0000000000..a61ea7f583 --- /dev/null +++ b/packages/backend-core/src/objectStore/cloudfront.ts @@ -0,0 +1,41 @@ +import env from "../environment" +const cfsign = require("aws-cloudfront-sign") + +let PRIVATE_KEY: string | undefined + +function getPrivateKey() { + if (!env.CLOUDFRONT_PRIVATE_KEY_64) { + throw new Error("CLOUDFRONT_PRIVATE_KEY_64 is not set") + } + + if (PRIVATE_KEY) { + return PRIVATE_KEY + } + + PRIVATE_KEY = Buffer.from(env.CLOUDFRONT_PRIVATE_KEY_64, "base64").toString( + "utf-8" + ) + + return PRIVATE_KEY +} + +const getCloudfrontSignParams = () => { + return { + keypairId: env.CLOUDFRONT_PUBLIC_KEY_ID, + privateKeyString: getPrivateKey(), + expireTime: new Date().getTime() + 1000 * 60 * 60, // 1 hour + } +} + +export const getPresignedUrl = (s3Key: string) => { + const url = getUrl(s3Key) + return cfsign.getSignedUrl(url, getCloudfrontSignParams()) +} + +export const getUrl = (s3Key: string) => { + let prefix = "/" + if (s3Key.startsWith("/")) { + prefix = "" + } + return `${env.CLOUDFRONT_CDN}${prefix}${s3Key}` +} diff --git a/packages/backend-core/src/objectStore/index.ts b/packages/backend-core/src/objectStore/index.ts index 17e002cc49..02c99828dd 100644 --- a/packages/backend-core/src/objectStore/index.ts +++ b/packages/backend-core/src/objectStore/index.ts @@ -1,344 +1,3 @@ -const sanitize = require("sanitize-s3-objectkey") -import AWS from "aws-sdk" -import stream from "stream" -import fetch from "node-fetch" -import tar from "tar-fs" -const zlib = require("zlib") -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 -const STATE = { - bucketCreationPromises: {}, -} - -const CONTENT_TYPE_MAP: any = { - html: "text/html", - css: "text/css", - js: "application/javascript", - json: "application/json", -} -const STRING_CONTENT_TYPES = [ - CONTENT_TYPE_MAP.html, - CONTENT_TYPE_MAP.css, - CONTENT_TYPE_MAP.js, - CONTENT_TYPE_MAP.json, -] - -// does normal sanitization and then swaps dev apps to apps -export function sanitizeKey(input: any) { - return sanitize(sanitizeBucket(input)).replace(/\\/g, "/") -} - -// simply handles the dev app to app conversion -export function sanitizeBucket(input: any) { - return input.replace(new RegExp(APP_DEV_PREFIX, "g"), APP_PREFIX) -} - -function publicPolicy(bucketName: any) { - return { - Version: "2012-10-17", - Statement: [ - { - Effect: "Allow", - Principal: { - AWS: ["*"], - }, - Action: "s3:GetObject", - Resource: [`arn:aws:s3:::${bucketName}/*`], - }, - ], - } -} - -const PUBLIC_BUCKETS = [ - ObjectStoreBuckets.APPS, - ObjectStoreBuckets.GLOBAL, - ObjectStoreBuckets.PLUGINS, -] - -/** - * Gets a connection to the object store using the S3 SDK. - * @param {string} bucket the name of the bucket which blobs will be uploaded/retrieved from. - * @return {Object} an S3 object store object, check S3 Nodejs SDK for usage. - * @constructor - */ -export const ObjectStore = (bucket: any) => { - const config: any = { - s3ForcePathStyle: true, - signatureVersion: "v4", - apiVersion: "2006-03-01", - accessKeyId: env.MINIO_ACCESS_KEY, - secretAccessKey: env.MINIO_SECRET_KEY, - region: env.AWS_REGION, - } - if (bucket) { - config.params = { - Bucket: sanitizeBucket(bucket), - } - } - if (env.MINIO_URL) { - config.endpoint = env.MINIO_URL - } - return new AWS.S3(config) -} - -/** - * 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. - */ -export const makeSureBucketExists = async (client: any, bucketName: any) => { - bucketName = sanitizeBucket(bucketName) - try { - await client - .headBucket({ - Bucket: bucketName, - }) - .promise() - } catch (err: any) { - const promises: any = STATE.bucketCreationPromises - const doesntExist = err.statusCode === 404, - noAccess = err.statusCode === 403 - if (promises[bucketName]) { - await promises[bucketName] - } else if (doesntExist || noAccess) { - if (doesntExist) { - // bucket doesn't exist create it - promises[bucketName] = client - .createBucket({ - Bucket: bucketName, - }) - .promise() - await promises[bucketName] - delete promises[bucketName] - } - // public buckets are quite hidden in the system, make sure - // no bucket is set accidentally - if (PUBLIC_BUCKETS.includes(bucketName)) { - await client - .putBucketPolicy({ - Bucket: bucketName, - Policy: JSON.stringify(publicPolicy(bucketName)), - }) - .promise() - } - } else { - throw new Error("Unable to write to object store bucket.") - } - } -} - -/** - * Uploads the contents of a file given the required parameters, useful when - * temp files in use (for example file uploaded as an attachment). - */ -export const upload = async ({ - bucket: bucketName, - filename, - path, - type, - metadata, -}: any) => { - const extension = [...filename.split(".")].pop() - const fileBytes = fs.readFileSync(path) - - const objectStore = ObjectStore(bucketName) - await makeSureBucketExists(objectStore, bucketName) - - const config: any = { - // windows file paths need to be converted to forward slashes for s3 - Key: sanitizeKey(filename), - Body: fileBytes, - ContentType: type || CONTENT_TYPE_MAP[extension.toLowerCase()], - } - if (metadata) { - config.Metadata = metadata - } - return objectStore.upload(config).promise() -} - -/** - * Similar to the upload function but can be used to send a file stream - * through to the object store. - */ -export const streamUpload = async ( - bucketName: any, - filename: any, - stream: any, - extra = {} -) => { - const objectStore = ObjectStore(bucketName) - await makeSureBucketExists(objectStore, bucketName) - - // Set content type for certain known extensions - if (filename?.endsWith(".js")) { - extra = { - ...extra, - ContentType: "application/javascript", - } - } else if (filename?.endsWith(".svg")) { - extra = { - ...extra, - ContentType: "image", - } - } - - const params = { - Bucket: sanitizeBucket(bucketName), - Key: sanitizeKey(filename), - Body: stream, - ...extra, - } - return objectStore.upload(params).promise() -} - -/** - * 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. - */ -export const retrieve = async (bucketName: any, filepath: any) => { - const objectStore = ObjectStore(bucketName) - const params = { - Bucket: sanitizeBucket(bucketName), - Key: sanitizeKey(filepath), - } - 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") - } else { - return response.Body - } -} - -/** - * Same as retrieval function but puts to a temporary file. - */ -export const retrieveToTmp = async (bucketName: any, filepath: any) => { - bucketName = sanitizeBucket(bucketName) - filepath = sanitizeKey(filepath) - const data = await retrieve(bucketName, filepath) - const outputPath = join(budibaseTempDir(), v4()) - fs.writeFileSync(outputPath, data) - return outputPath -} - -/** - * Delete a single file. - */ -export const deleteFile = async (bucketName: any, filepath: any) => { - const objectStore = ObjectStore(bucketName) - await makeSureBucketExists(objectStore, bucketName) - const params = { - Bucket: bucketName, - Key: filepath, - } - return objectStore.deleteObject(params) -} - -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: any) => ({ Key: path })), - }, - } - return objectStore.deleteObjects(params).promise() -} - -/** - * Delete a path, including everything within. - */ -export const deleteFolder = async ( - bucketName: any, - folder: any -): Promise => { - bucketName = sanitizeBucket(bucketName) - folder = sanitizeKey(folder) - const client = ObjectStore(bucketName) - const listParams = { - Bucket: bucketName, - Prefix: folder, - } - - let response: any = await client.listObjects(listParams).promise() - if (response.Contents.length === 0) { - return - } - const deleteParams: any = { - Bucket: bucketName, - Delete: { - Objects: [], - }, - } - - 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 deleteFolder(bucketName, folder) - } -} - -export const uploadDirectory = async ( - bucketName: any, - localPath: any, - bucketPath: any -) => { - bucketName = sanitizeBucket(bucketName) - let uploads = [] - const files = fs.readdirSync(localPath, { withFileTypes: true }) - for (let file of files) { - const path = sanitizeKey(join(bucketPath, file.name)) - const local = join(localPath, file.name) - if (file.isDirectory()) { - uploads.push(uploadDirectory(bucketName, local, path)) - } else { - uploads.push(streamUpload(bucketName, path, fs.createReadStream(local))) - } - } - await Promise.all(uploads) - return files -} - -exports.downloadTarballDirect = async ( - url: string, - path: string, - headers = {} -) => { - path = sanitizeKey(path) - const response = await fetch(url, { headers }) - 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) - if (!response.ok) { - throw new Error(`unexpected response ${response.statusText}`) - } - - const tmpPath = join(budibaseTempDir(), path) - await streamPipeline(response.body, zlib.Unzip(), tar.extract(tmpPath)) - if (!env.isTest() && env.SELF_HOSTED) { - await uploadDirectory(bucketName, tmpPath, path) - } - // return the temporary path incase there is a use for it - return tmpPath -} +export * from "./objectStore" +export * from "./utils" +export * from "./buckets" diff --git a/packages/backend-core/src/objectStore/objectStore.ts b/packages/backend-core/src/objectStore/objectStore.ts new file mode 100644 index 0000000000..f601d40a37 --- /dev/null +++ b/packages/backend-core/src/objectStore/objectStore.ts @@ -0,0 +1,440 @@ +const sanitize = require("sanitize-s3-objectkey") +import AWS from "aws-sdk" +import stream from "stream" +import fetch from "node-fetch" +import tar from "tar-fs" +const zlib = require("zlib") +import { promisify } from "util" +import { join } from "path" +import fs from "fs" +import env from "../environment" +import { budibaseTempDir } from "./utils" +import { v4 } from "uuid" +import { APP_PREFIX, APP_DEV_PREFIX } from "../db" + +const streamPipeline = promisify(stream.pipeline) +// use this as a temporary store of buckets that are being created +const STATE = { + bucketCreationPromises: {}, +} + +type ListParams = { + ContinuationToken?: string +} + +type UploadParams = { + bucket: string + filename: string + path: string + type?: string | null + // can be undefined, we will remove it + metadata?: { + [key: string]: string | undefined + } +} + +const CONTENT_TYPE_MAP: any = { + txt: "text/plain", + html: "text/html", + css: "text/css", + js: "application/javascript", + json: "application/json", + gz: "application/gzip", +} + +const STRING_CONTENT_TYPES = [ + CONTENT_TYPE_MAP.html, + CONTENT_TYPE_MAP.css, + CONTENT_TYPE_MAP.js, + CONTENT_TYPE_MAP.json, +] + +// does normal sanitization and then swaps dev apps to apps +export function sanitizeKey(input: string) { + return sanitize(sanitizeBucket(input)).replace(/\\/g, "/") +} + +// simply handles the dev app to app conversion +export function sanitizeBucket(input: string) { + return input.replace(new RegExp(APP_DEV_PREFIX, "g"), APP_PREFIX) +} + +/** + * Gets a connection to the object store using the S3 SDK. + * @param {string} bucket the name of the bucket which blobs will be uploaded/retrieved from. + * @param {object} opts configuration for the object store. + * @return {Object} an S3 object store object, check S3 Nodejs SDK for usage. + * @constructor + */ +export const ObjectStore = ( + bucket: string, + opts: { presigning: boolean } = { presigning: false } +) => { + const config: any = { + s3ForcePathStyle: true, + signatureVersion: "v4", + apiVersion: "2006-03-01", + accessKeyId: env.MINIO_ACCESS_KEY, + secretAccessKey: env.MINIO_SECRET_KEY, + region: env.AWS_REGION, + } + if (bucket) { + config.params = { + Bucket: sanitizeBucket(bucket), + } + } + + // custom S3 is in use i.e. minio + if (env.MINIO_URL) { + if (opts.presigning && env.MINIO_ENABLED) { + // IMPORTANT: Signed urls will inspect the host header of the request. + // Normally a signed url will need to be generated with a specified host in mind. + // To support dynamic hosts, e.g. some unknown self-hosted installation url, + // use a predefined host. The host 'minio-service' is also forwarded to minio requests via nginx + config.endpoint = "minio-service" + } else { + config.endpoint = env.MINIO_URL + } + } + + return new AWS.S3(config) +} + +/** + * 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. + */ +export const makeSureBucketExists = async (client: any, bucketName: string) => { + bucketName = sanitizeBucket(bucketName) + try { + await client + .headBucket({ + Bucket: bucketName, + }) + .promise() + } catch (err: any) { + const promises: any = STATE.bucketCreationPromises + const doesntExist = err.statusCode === 404, + noAccess = err.statusCode === 403 + if (promises[bucketName]) { + await promises[bucketName] + } else if (doesntExist || noAccess) { + if (doesntExist) { + // bucket doesn't exist create it + promises[bucketName] = client + .createBucket({ + Bucket: bucketName, + }) + .promise() + await promises[bucketName] + delete promises[bucketName] + } + } else { + throw new Error("Unable to write to object store bucket.") + } + } +} + +/** + * Uploads the contents of a file given the required parameters, useful when + * temp files in use (for example file uploaded as an attachment). + */ +export const upload = async ({ + bucket: bucketName, + filename, + path, + type, + metadata, +}: UploadParams) => { + const extension = filename.split(".").pop() + const fileBytes = fs.readFileSync(path) + + const objectStore = ObjectStore(bucketName) + await makeSureBucketExists(objectStore, bucketName) + + let contentType = type + if (!contentType) { + contentType = extension + ? CONTENT_TYPE_MAP[extension.toLowerCase()] + : CONTENT_TYPE_MAP.txt + } + const config: any = { + // windows file paths need to be converted to forward slashes for s3 + Key: sanitizeKey(filename), + Body: fileBytes, + ContentType: contentType, + } + if (metadata && typeof metadata === "object") { + // remove any nullish keys from the metadata object, as these may be considered invalid + for (let key of Object.keys(metadata)) { + if (!metadata[key] || typeof metadata[key] !== "string") { + delete metadata[key] + } + } + config.Metadata = metadata + } + return objectStore.upload(config).promise() +} + +/** + * Similar to the upload function but can be used to send a file stream + * through to the object store. + */ +export const streamUpload = async ( + bucketName: string, + filename: string, + stream: any, + extra = {} +) => { + const objectStore = ObjectStore(bucketName) + await makeSureBucketExists(objectStore, bucketName) + + // Set content type for certain known extensions + if (filename?.endsWith(".js")) { + extra = { + ...extra, + ContentType: "application/javascript", + } + } else if (filename?.endsWith(".svg")) { + extra = { + ...extra, + ContentType: "image", + } + } + + const params = { + Bucket: sanitizeBucket(bucketName), + Key: sanitizeKey(filename), + Body: stream, + ...extra, + } + return objectStore.upload(params).promise() +} + +/** + * 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. + */ +export const retrieve = async (bucketName: string, filepath: string) => { + const objectStore = ObjectStore(bucketName) + const params = { + Bucket: sanitizeBucket(bucketName), + Key: sanitizeKey(filepath), + } + 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") + } else { + return response.Body + } +} + +export const listAllObjects = async (bucketName: string, path: string) => { + const objectStore = ObjectStore(bucketName) + const list = (params: ListParams = {}) => { + return objectStore + .listObjectsV2({ + ...params, + Bucket: sanitizeBucket(bucketName), + Prefix: sanitizeKey(path), + }) + .promise() + } + let isTruncated = false, + token, + objects: AWS.S3.Types.Object[] = [] + do { + let params: ListParams = {} + if (token) { + params.ContinuationToken = token + } + const response = await list(params) + if (response.Contents) { + objects = objects.concat(response.Contents) + } + isTruncated = !!response.IsTruncated + } while (isTruncated) + return objects +} + +/** + * Generate a presigned url with a default TTL of 1 hour + */ +export const getPresignedUrl = ( + bucketName: string, + key: string, + durationSeconds: number = 3600 +) => { + const objectStore = ObjectStore(bucketName, { presigning: true }) + const params = { + Bucket: sanitizeBucket(bucketName), + Key: sanitizeKey(key), + Expires: durationSeconds, + } + const url = objectStore.getSignedUrl("getObject", params) + + if (!env.MINIO_ENABLED) { + // return the full URL to the client + return url + } else { + // return the path only to the client + // use the presigned url route to ensure the static + // hostname will be used in the request + const signedUrl = new URL(url) + const path = signedUrl.pathname + const query = signedUrl.search + return `/files/signed${path}${query}` + } +} + +/** + * Same as retrieval function but puts to a temporary file. + */ +export const retrieveToTmp = async (bucketName: string, filepath: string) => { + bucketName = sanitizeBucket(bucketName) + filepath = sanitizeKey(filepath) + const data = await retrieve(bucketName, filepath) + const outputPath = join(budibaseTempDir(), v4()) + fs.writeFileSync(outputPath, data) + return outputPath +} + +export const retrieveDirectory = async (bucketName: string, path: string) => { + let writePath = join(budibaseTempDir(), v4()) + fs.mkdirSync(writePath) + const objects = await listAllObjects(bucketName, path) + let fullObjects = await Promise.all( + objects.map(obj => retrieve(bucketName, obj.Key!)) + ) + let count = 0 + for (let obj of objects) { + const filename = obj.Key! + const data = fullObjects[count++] + const possiblePath = filename.split("/") + if (possiblePath.length > 1) { + const dirs = possiblePath.slice(0, possiblePath.length - 1) + fs.mkdirSync(join(writePath, ...dirs), { recursive: true }) + } + fs.writeFileSync(join(writePath, ...possiblePath), data) + } + return writePath +} + +/** + * Delete a single file. + */ +export const deleteFile = async (bucketName: string, filepath: string) => { + const objectStore = ObjectStore(bucketName) + await makeSureBucketExists(objectStore, bucketName) + const params = { + Bucket: bucketName, + Key: sanitizeKey(filepath), + } + return objectStore.deleteObject(params).promise() +} + +export const deleteFiles = async (bucketName: string, filepaths: string[]) => { + const objectStore = ObjectStore(bucketName) + await makeSureBucketExists(objectStore, bucketName) + const params = { + Bucket: bucketName, + Delete: { + Objects: filepaths.map((path: any) => ({ Key: sanitizeKey(path) })), + }, + } + return objectStore.deleteObjects(params).promise() +} + +/** + * Delete a path, including everything within. + */ +export const deleteFolder = async ( + bucketName: string, + folder: string +): Promise => { + bucketName = sanitizeBucket(bucketName) + folder = sanitizeKey(folder) + const client = ObjectStore(bucketName) + const listParams = { + Bucket: bucketName, + Prefix: folder, + } + + let response: any = await client.listObjects(listParams).promise() + if (response.Contents.length === 0) { + return + } + const deleteParams: any = { + Bucket: bucketName, + Delete: { + Objects: [], + }, + } + + 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 deleteFolder(bucketName, folder) + } +} + +export const uploadDirectory = async ( + bucketName: string, + localPath: string, + bucketPath: string +) => { + bucketName = sanitizeBucket(bucketName) + let uploads = [] + const files = fs.readdirSync(localPath, { withFileTypes: true }) + for (let file of files) { + const path = sanitizeKey(join(bucketPath, file.name)) + const local = join(localPath, file.name) + if (file.isDirectory()) { + uploads.push(uploadDirectory(bucketName, local, path)) + } else { + uploads.push(streamUpload(bucketName, path, fs.createReadStream(local))) + } + } + await Promise.all(uploads) + return files +} + +export const downloadTarballDirect = async ( + url: string, + path: string, + headers = {} +) => { + path = sanitizeKey(path) + const response = await fetch(url, { headers }) + if (!response.ok) { + throw new Error(`unexpected response ${response.statusText}`) + } + + await streamPipeline(response.body, zlib.Unzip(), tar.extract(path)) +} + +export const downloadTarball = async ( + url: string, + bucketName: string, + path: string +) => { + bucketName = sanitizeBucket(bucketName) + path = sanitizeKey(path) + const response = await fetch(url) + if (!response.ok) { + throw new Error(`unexpected response ${response.statusText}`) + } + + const tmpPath = join(budibaseTempDir(), path) + await streamPipeline(response.body, zlib.Unzip(), tar.extract(tmpPath)) + if (!env.isTest() && env.SELF_HOSTED) { + 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.ts similarity index 55% rename from packages/backend-core/src/objectStore/utils.js rename to packages/backend-core/src/objectStore/utils.ts index 9cf4f5f70e..dba5f3d1c2 100644 --- a/packages/backend-core/src/objectStore/utils.js +++ b/packages/backend-core/src/objectStore/utils.ts @@ -1,21 +1,27 @@ -const { join } = require("path") -const { tmpdir } = require("os") -const env = require("../environment") +import { join } from "path" +import { tmpdir } from "os" +import fs from "fs" +import env from "../environment" /**************************************************** * NOTE: When adding a new bucket - name * * sure that S3 usages (like budibase-infra) * * have been updated to have a unique bucket name. * ****************************************************/ -exports.ObjectStoreBuckets = { +// can't be an enum - only numbers can be used for computed types +export const ObjectStoreBuckets = { 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, PLUGINS: env.PLUGIN_BUCKET_NAME, } -exports.budibaseTempDir = function () { - return join(tmpdir(), ".budibase") +const bbTmp = join(tmpdir(), ".budibase") +if (!fs.existsSync(bbTmp)) { + fs.mkdirSync(bbTmp) +} + +export function budibaseTempDir() { + return bbTmp } diff --git a/packages/backend-core/src/pino.js b/packages/backend-core/src/pino.js deleted file mode 100644 index 69962b3841..0000000000 --- a/packages/backend-core/src/pino.js +++ /dev/null @@ -1,11 +0,0 @@ -const env = require("./environment") - -exports.pinoSettings = () => ({ - prettyPrint: { - levelFirst: true, - }, - level: env.LOG_LEVEL || "error", - autoLogging: { - ignore: req => req.url.includes("/health"), - }, -}) diff --git a/packages/backend-core/src/pkg/cache.ts b/packages/backend-core/src/pkg/cache.ts deleted file mode 100644 index 1aaa40370d..0000000000 --- a/packages/backend-core/src/pkg/cache.ts +++ /dev/null @@ -1,11 +0,0 @@ -// 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 deleted file mode 100644 index 5caa82ab0c..0000000000 --- a/packages/backend-core/src/pkg/context.ts +++ /dev/null @@ -1,24 +0,0 @@ -// 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 deleted file mode 100644 index 0254adddd5..0000000000 --- a/packages/backend-core/src/pkg/db.ts +++ /dev/null @@ -1,7 +0,0 @@ -// 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 deleted file mode 100644 index 0447c6b3c2..0000000000 --- a/packages/backend-core/src/pkg/objectStore.ts +++ /dev/null @@ -1,4 +0,0 @@ -// 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 deleted file mode 100644 index 65ab186d9a..0000000000 --- a/packages/backend-core/src/pkg/redis.ts +++ /dev/null @@ -1,11 +0,0 @@ -// 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 deleted file mode 100644 index 5272046524..0000000000 --- a/packages/backend-core/src/pkg/utils.ts +++ /dev/null @@ -1,4 +0,0 @@ -// 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/plugin/index.ts b/packages/backend-core/src/plugin/index.ts index a6d1853007..3eeaeaa90c 100644 --- a/packages/backend-core/src/plugin/index.ts +++ b/packages/backend-core/src/plugin/index.ts @@ -1,7 +1 @@ -import * as utils from "./utils" - -const pkg = { - ...utils, -} - -export = pkg +export * from "./utils" diff --git a/packages/backend-core/src/plugin/utils.js b/packages/backend-core/src/plugin/utils.ts similarity index 87% rename from packages/backend-core/src/plugin/utils.js rename to packages/backend-core/src/plugin/utils.ts index 60a40f3a76..7b62248bb5 100644 --- a/packages/backend-core/src/plugin/utils.js +++ b/packages/backend-core/src/plugin/utils.ts @@ -1,9 +1,5 @@ -const { - DatasourceFieldType, - QueryType, - PluginType, -} = require("@budibase/types") -const joi = require("joi") +import { DatasourceFieldType, QueryType, PluginType } from "@budibase/types" +import joi from "joi" const DATASOURCE_TYPES = [ "Relational", @@ -14,14 +10,14 @@ const DATASOURCE_TYPES = [ "API", ] -function runJoi(validator, schema) { +function runJoi(validator: joi.Schema, schema: any) { const { error } = validator.validate(schema) if (error) { throw error } } -function validateComponent(schema) { +function validateComponent(schema: any) { const validator = joi.object({ type: joi.string().allow("component").required(), metadata: joi.object().unknown(true).required(), @@ -37,7 +33,7 @@ function validateComponent(schema) { runJoi(validator, schema) } -function validateDatasource(schema) { +function validateDatasource(schema: any) { const fieldValidator = joi.object({ type: joi .string() @@ -51,6 +47,7 @@ function validateDatasource(schema) { const queryValidator = joi .object({ type: joi.string().allow(...Object.values(QueryType)), + readable: joi.boolean(), fields: joi.object().pattern(joi.string(), fieldValidator), }) .required() @@ -85,7 +82,7 @@ function validateDatasource(schema) { runJoi(validator, schema) } -exports.validate = schema => { +export function validate(schema: any) { switch (schema?.type) { case PluginType.COMPONENT: validateComponent(schema) diff --git a/packages/backend-core/src/queue/constants.ts b/packages/backend-core/src/queue/constants.ts new file mode 100644 index 0000000000..e8323dacb8 --- /dev/null +++ b/packages/backend-core/src/queue/constants.ts @@ -0,0 +1,4 @@ +export enum JobQueue { + AUTOMATION = "automationQueue", + APP_BACKUP = "appBackupQueue", +} diff --git a/packages/server/src/utilities/queue/inMemoryQueue.js b/packages/backend-core/src/queue/inMemoryQueue.ts similarity index 83% rename from packages/server/src/utilities/queue/inMemoryQueue.js rename to packages/backend-core/src/queue/inMemoryQueue.ts index 79781f9283..b80aece418 100644 --- a/packages/server/src/utilities/queue/inMemoryQueue.js +++ b/packages/backend-core/src/queue/inMemoryQueue.ts @@ -1,4 +1,5 @@ -let events = require("events") +import events from "events" +import { timeout } from "../utils" /** * Bull works with a Job wrapper around all messages that contains a lot more information about @@ -9,7 +10,7 @@ let events = require("events") * @returns {Object} A new job which can now be put onto the queue, this is mostly an * internal structure so that an in memory queue can be easily swapped for a Bull queue. */ -function newJob(queue, message) { +function newJob(queue: string, message: any) { return { timestamp: Date.now(), queue: queue, @@ -23,17 +24,25 @@ function newJob(queue, message) { * to the consumers - in can support many inputs and many consumers. */ class InMemoryQueue { + _name: string + _opts?: any + _messages: any[] + _emitter: EventEmitter + _runCount: number + _addCount: number /** * The constructor the queue, exactly the same as that of Bulls. * @param {string} name The name of the queue which is being configured. * @param {object|null} opts This is not used by the in memory queue as there is no real use * case when in memory, but is the same API as Bull */ - constructor(name, opts = null) { + constructor(name: string, opts = null) { this._name = name this._opts = opts this._messages = [] this._emitter = new events.EventEmitter() + this._runCount = 0 + this._addCount = 0 } /** @@ -45,7 +54,7 @@ class InMemoryQueue { * note this is incredibly limited compared to Bull as in reality the Job would contain * a lot more information about the queue and current status of Bull cluster. */ - process(func) { + process(func: any) { this._emitter.on("message", async () => { if (this._messages.length <= 0) { return @@ -55,6 +64,7 @@ class InMemoryQueue { if (resp.then != null) { await resp } + this._runCount++ }) } @@ -65,13 +75,15 @@ class InMemoryQueue { * return the message to a consumer (if one is attached). * @param {object} msg A message to be transported over the queue, this should be * a JSON message as this is required by Bull. + * @param {boolean} repeat serves no purpose for the import queue. */ // eslint-disable-next-line no-unused-vars - add(msg, repeat) { + add(msg: any, repeat: boolean) { if (typeof msg !== "object") { throw "Queue only supports carrying JSON." } this._messages.push(newJob(this._name, msg)) + this._addCount++ this._emitter.emit("message") } @@ -86,7 +98,7 @@ class InMemoryQueue { * This removes a cron which has been implemented, this is part of Bull API. * @param {string} cronJobId The cron which is to be removed. */ - removeRepeatableByKey(cronJobId) { + removeRepeatableByKey(cronJobId: string) { // TODO: implement for testing console.log(cronJobId) } @@ -99,7 +111,7 @@ class InMemoryQueue { } // eslint-disable-next-line no-unused-vars - removeJobs(pattern) { + removeJobs(pattern: string) { // no-op } @@ -117,6 +129,12 @@ class InMemoryQueue { on() { // do nothing } + + async waitForCompletion() { + do { + await timeout(50) + } while (this._addCount < this._runCount) + } } -module.exports = InMemoryQueue +export default InMemoryQueue diff --git a/packages/backend-core/src/queue/index.ts b/packages/backend-core/src/queue/index.ts new file mode 100644 index 0000000000..b7d565ba13 --- /dev/null +++ b/packages/backend-core/src/queue/index.ts @@ -0,0 +1,2 @@ +export * from "./queue" +export * from "./constants" diff --git a/packages/backend-core/src/queue/listeners.ts b/packages/backend-core/src/queue/listeners.ts new file mode 100644 index 0000000000..e1975b5d06 --- /dev/null +++ b/packages/backend-core/src/queue/listeners.ts @@ -0,0 +1,101 @@ +import { Job, JobId, Queue } from "bull" +import { JobQueue } from "./constants" + +export type StalledFn = (job: Job) => Promise + +export function addListeners( + queue: Queue, + jobQueue: JobQueue, + removeStalledCb?: StalledFn +) { + logging(queue, jobQueue) + if (removeStalledCb) { + handleStalled(queue, removeStalledCb) + } +} + +function handleStalled(queue: Queue, removeStalledCb?: StalledFn) { + queue.on("stalled", async (job: Job) => { + if (removeStalledCb) { + await removeStalledCb(job) + } else if (job.opts.repeat) { + const jobId = job.id + const repeatJobs = await queue.getRepeatableJobs() + for (let repeatJob of repeatJobs) { + if (repeatJob.id === jobId) { + await queue.removeRepeatableByKey(repeatJob.key) + } + } + console.log(`jobId=${jobId} disabled`) + } + }) +} + +function logging(queue: Queue, jobQueue: JobQueue) { + let eventType: string + switch (jobQueue) { + case JobQueue.AUTOMATION: + eventType = "automation-event" + break + case JobQueue.APP_BACKUP: + eventType = "app-backup-event" + break + } + if (process.env.NODE_DEBUG?.includes("bull")) { + queue + .on("error", (error: any) => { + // An error occurred. + console.error(`${eventType}=error error=${JSON.stringify(error)}`) + }) + .on("waiting", (jobId: JobId) => { + // A Job is waiting to be processed as soon as a worker is idling. + console.log(`${eventType}=waiting jobId=${jobId}`) + }) + .on("active", (job: Job, jobPromise: any) => { + // A job has started. You can use `jobPromise.cancel()`` to abort it. + console.log(`${eventType}=active jobId=${job.id}`) + }) + .on("stalled", (job: Job) => { + // A job has been marked as stalled. This is useful for debugging job + // workers that crash or pause the event loop. + console.error( + `${eventType}=stalled jobId=${job.id} job=${JSON.stringify(job)}` + ) + }) + .on("progress", (job: Job, progress: any) => { + // A job's progress was updated! + console.log( + `${eventType}=progress jobId=${job.id} progress=${progress}` + ) + }) + .on("completed", (job: Job, result) => { + // A job successfully completed with a `result`. + console.log(`${eventType}=completed jobId=${job.id} result=${result}`) + }) + .on("failed", (job, err: any) => { + // A job failed with reason `err`! + console.log(`${eventType}=failed jobId=${job.id} error=${err}`) + }) + .on("paused", () => { + // The queue has been paused. + console.log(`${eventType}=paused`) + }) + .on("resumed", (job: Job) => { + // The queue has been resumed. + console.log(`${eventType}=paused jobId=${job.id}`) + }) + .on("cleaned", (jobs: Job[], type: string) => { + // Old jobs have been cleaned from the queue. `jobs` is an array of cleaned + // jobs, and `type` is the type of jobs cleaned. + console.log(`${eventType}=cleaned length=${jobs.length} type=${type}`) + }) + .on("drained", () => { + // Emitted every time the queue has processed all the waiting jobs (even if there can be some delayed jobs not yet processed) + console.log(`${eventType}=drained`) + }) + .on("removed", (job: Job) => { + // A job successfully removed. + console.log(`${eventType}=removed jobId=${job.id}`) + }) + } +} diff --git a/packages/backend-core/src/queue/queue.ts b/packages/backend-core/src/queue/queue.ts new file mode 100644 index 0000000000..b34d46e463 --- /dev/null +++ b/packages/backend-core/src/queue/queue.ts @@ -0,0 +1,51 @@ +import env from "../environment" +import { getRedisOptions } from "../redis/utils" +import { JobQueue } from "./constants" +import InMemoryQueue from "./inMemoryQueue" +import BullQueue from "bull" +import { addListeners, StalledFn } from "./listeners" +const { opts: redisOpts, redisProtocolUrl } = getRedisOptions() + +const CLEANUP_PERIOD_MS = 60 * 1000 +let QUEUES: BullQueue.Queue[] | InMemoryQueue[] = [] +let cleanupInterval: NodeJS.Timeout + +async function cleanup() { + for (let queue of QUEUES) { + await queue.clean(CLEANUP_PERIOD_MS, "completed") + } +} + +export function createQueue( + jobQueue: JobQueue, + opts: { removeStalledCb?: StalledFn } = {} +): BullQueue.Queue { + const queueConfig: any = redisProtocolUrl || { redis: redisOpts } + let queue: any + if (!env.isTest()) { + queue = new BullQueue(jobQueue, queueConfig) + } else { + queue = new InMemoryQueue(jobQueue, queueConfig) + } + addListeners(queue, jobQueue, opts?.removeStalledCb) + QUEUES.push(queue) + if (!cleanupInterval) { + cleanupInterval = setInterval(cleanup, CLEANUP_PERIOD_MS) + // fire off an initial cleanup + cleanup().catch(err => { + console.error(`Unable to cleanup automation queue initially - ${err}`) + }) + } + return queue +} + +export async function shutdown() { + if (QUEUES.length) { + clearInterval(cleanupInterval) + for (let queue of QUEUES) { + await queue.close() + } + QUEUES = [] + } + console.log("Queues shutdown") +} diff --git a/packages/backend-core/src/redis/index.ts b/packages/backend-core/src/redis/index.ts index 206110366f..ea4379f048 100644 --- a/packages/backend-core/src/redis/index.ts +++ b/packages/backend-core/src/redis/index.ts @@ -1,250 +1,6 @@ -import RedisWrapper from "../redis" -const env = require("../environment") -// ioredis mock is all in memory -const Redis = env.isTest() ? require("ioredis-mock") : require("ioredis") -const { - addDbPrefix, - 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 CLIENTS: { [key: number]: any } = {} -// if in test always connected -let CONNECTED = env.isTest() - -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 - } - pickClient(selectDb).disconnect() - CLOSED = true - // always clear this on error - clearTimeout(timeout) - CONNECTED = false - console.error("Redis connection failed - " + err) - setTimeout(() => { - init() - }, RETRY_PERIOD_MS) -} - -/** - * 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(selectDb = DEFAULT_SELECT_DB) { - let timeout: NodeJS.Timeout - CLOSED = false - 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( - selectDb, - timeout, - "Did not successfully connect in timeout" - ) - } - }, STARTUP_TIMEOUT_MS) - - // disconnect any lingering client - if (client) { - client.disconnect() - } - const { redisProtocolUrl, opts, host, port } = getRedisOptions(CLUSTERED) - - if (CLUSTERED) { - client = new Redis.Cluster([{ host, port }], opts) - } else if (redisProtocolUrl) { - client = new Redis(redisProtocolUrl) - } else { - client = new Redis(opts) - } - // attach handlers - client.on("end", (err: Error) => { - connectionError(selectDb, timeout, err) - }) - client.on("error", (err: Error) => { - connectionError(selectDb, timeout, err) - }) - client.on("connect", () => { - clearTimeout(timeout) - CONNECTED = true - }) - CLIENTS[selectDb] = client -} - -function waitForConnection(selectDb: number = DEFAULT_SELECT_DB) { - return new Promise(resolve => { - if (pickClient(selectDb) == null) { - init() - } else if (CONNECTED) { - resolve("") - return - } - // check if the connection is ready - const interval = setInterval(() => { - if (CONNECTED) { - clearInterval(interval) - resolve("") - } - }, 500) - }) -} - -/** - * 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: any, client: RedisWrapper) { - return new Promise((resolve, reject) => { - const outputKeys = new Set() - stream.on("data", (keys: string[]) => { - keys.forEach(key => { - outputKeys.add(key) - }) - }) - stream.on("error", (err: Error) => { - reject(err) - }) - stream.on("end", async () => { - const keysArray: string[] = Array.from(outputKeys) as string[] - try { - let getPromises = [] - for (let key of keysArray) { - getPromises.push(client.get(key)) - } - const jsonArray = await Promise.all(getPromises) - resolve( - keysArray.map(key => ({ - key: removeDbPrefix(key), - value: JSON.parse(jsonArray.shift()), - })) - ) - } catch (err) { - reject(err) - } - }) - }) -} - -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(this._select) - await waitForConnection(this._select) - return this - } - - async finish() { - CLOSED = true - this.getClient().disconnect() - } - - async scan(key = ""): Promise { - const db = this._db - key = `${db}${SEPARATOR}${key}` - let stream - if (CLUSTERED) { - let node = this.getClient().nodes("master") - stream = node[0].scanStream({ match: key + "*", count: 100 }) - } else { - stream = this.getClient().scanStream({ match: key + "*", count: 100 }) - } - return promisifyStream(stream, this.getClient()) - } - - async keys(pattern: string) { - const db = this._db - 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 - } - // if its not an object just return the response - try { - return JSON.parse(response) - } catch (err) { - return response - } - } - - 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 this.getClient().set(prefixedKey, value) - if (expirySeconds) { - await this.getClient().expire(prefixedKey, expirySeconds) - } - } - - async getTTL(key: string) { - const db = this._db - const prefixedKey = addDbPrefix(db, key) - return this.getClient().ttl(prefixedKey) - } - - async setExpiry(key: string, expirySeconds: number | null) { - const db = this._db - const prefixedKey = addDbPrefix(db, key) - await this.getClient().expire(prefixedKey, expirySeconds) - } - - async delete(key: string) { - const db = this._db - await this.getClient().del(addDbPrefix(db, key)) - } - - async clear() { - let items = await this.scan() - await Promise.all(items.map((obj: any) => this.delete(obj.key))) - } -} +// Mimic the outer package export for usage in index.ts +// The outer exports can't be used as they now reference dist directly +export { default as Client } from "./redis" +export * as utils from "./utils" +export * as clients from "./init" +export * as redlock from "./redlock" diff --git a/packages/backend-core/src/redis/init.js b/packages/backend-core/src/redis/init.js deleted file mode 100644 index 8e5d10f838..0000000000 --- a/packages/backend-core/src/redis/init.js +++ /dev/null @@ -1,72 +0,0 @@ -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/init.ts b/packages/backend-core/src/redis/init.ts new file mode 100644 index 0000000000..00329ffb84 --- /dev/null +++ b/packages/backend-core/src/redis/init.ts @@ -0,0 +1,72 @@ +import Client from "./redis" +import * as utils from "./utils" + +let userClient: Client, + sessionClient: Client, + appClient: Client, + cacheClient: Client, + writethroughClient: Client, + lockClient: Client + +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() + lockClient = await new Client(utils.Databases.LOCKS).init() + writethroughClient = await new Client( + utils.Databases.WRITE_THROUGH, + utils.SelectableDatabase.WRITE_THROUGH + ).init() +} + +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() + if (lockClient) await lockClient.finish() +}) + +export async function getUserClient() { + if (!userClient) { + await init() + } + return userClient +} + +export async function getSessionClient() { + if (!sessionClient) { + await init() + } + return sessionClient +} + +export async function getAppClient() { + if (!appClient) { + await init() + } + return appClient +} + +export async function getCacheClient() { + if (!cacheClient) { + await init() + } + return cacheClient +} + +export async function getWritethroughClient() { + if (!writethroughClient) { + await init() + } + return writethroughClient +} + +export async function getLockClient() { + if (!lockClient) { + await init() + } + return lockClient +} diff --git a/packages/backend-core/src/redis/redis.ts b/packages/backend-core/src/redis/redis.ts new file mode 100644 index 0000000000..0267709cdc --- /dev/null +++ b/packages/backend-core/src/redis/redis.ts @@ -0,0 +1,279 @@ +import env from "../environment" +// ioredis mock is all in memory +const Redis = env.isTest() ? require("ioredis-mock") : require("ioredis") +import { + addDbPrefix, + removeDbPrefix, + getRedisOptions, + SEPARATOR, + SelectableDatabase, +} from "./utils" + +const RETRY_PERIOD_MS = 2000 +const STARTUP_TIMEOUT_MS = 5000 +const CLUSTERED = false +const DEFAULT_SELECT_DB = SelectableDatabase.DEFAULT + +// for testing just generate the client once +let CLOSED = false +let CLIENTS: { [key: number]: any } = {} +// if in test always connected +let CONNECTED = env.isTest() + +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 + } + pickClient(selectDb).disconnect() + CLOSED = true + // always clear this on error + clearTimeout(timeout) + CONNECTED = false + console.error("Redis connection failed - " + err) + setTimeout(() => { + init() + }, RETRY_PERIOD_MS) +} + +/** + * 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(selectDb = DEFAULT_SELECT_DB) { + let timeout: NodeJS.Timeout + CLOSED = false + 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( + selectDb, + timeout, + "Did not successfully connect in timeout" + ) + } + }, STARTUP_TIMEOUT_MS) + + // disconnect any lingering client + if (client) { + client.disconnect() + } + const { redisProtocolUrl, opts, host, port } = getRedisOptions(CLUSTERED) + + if (CLUSTERED) { + client = new Redis.Cluster([{ host, port }], opts) + } else if (redisProtocolUrl) { + client = new Redis(redisProtocolUrl) + } else { + client = new Redis(opts) + } + // attach handlers + client.on("end", (err: Error) => { + connectionError(selectDb, timeout, err) + }) + client.on("error", (err: Error) => { + connectionError(selectDb, timeout, err) + }) + client.on("connect", () => { + clearTimeout(timeout) + CONNECTED = true + }) + CLIENTS[selectDb] = client +} + +function waitForConnection(selectDb: number = DEFAULT_SELECT_DB) { + return new Promise(resolve => { + if (pickClient(selectDb) == null) { + init() + } else if (CONNECTED) { + resolve("") + return + } + // check if the connection is ready + const interval = setInterval(() => { + if (CONNECTED) { + clearInterval(interval) + resolve("") + } + }, 500) + }) +} + +/** + * 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: any, client: RedisWrapper) { + return new Promise((resolve, reject) => { + const outputKeys = new Set() + stream.on("data", (keys: string[]) => { + keys.forEach(key => { + outputKeys.add(key) + }) + }) + stream.on("error", (err: Error) => { + reject(err) + }) + stream.on("end", async () => { + const keysArray: string[] = Array.from(outputKeys) as string[] + try { + let getPromises = [] + for (let key of keysArray) { + getPromises.push(client.get(key)) + } + const jsonArray = await Promise.all(getPromises) + resolve( + keysArray.map(key => ({ + key: removeDbPrefix(key), + value: JSON.parse(jsonArray.shift()), + })) + ) + } catch (err) { + reject(err) + } + }) + }) +} + +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(this._select) + await waitForConnection(this._select) + return this + } + + async finish() { + CLOSED = true + this.getClient().disconnect() + } + + async scan(key = ""): Promise { + const db = this._db + key = `${db}${SEPARATOR}${key}` + let stream + if (CLUSTERED) { + let node = this.getClient().nodes("master") + stream = node[0].scanStream({ match: key + "*", count: 100 }) + } else { + stream = this.getClient().scanStream({ match: key + "*", count: 100 }) + } + return promisifyStream(stream, this.getClient()) + } + + async keys(pattern: string) { + const db = this._db + 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 + } + // if its not an object just return the response + try { + return JSON.parse(response) + } catch (err) { + return response + } + } + + async bulkGet(keys: string[]) { + const db = this._db + if (keys.length === 0) { + return {} + } + const prefixedKeys = keys.map(key => addDbPrefix(db, key)) + let response = await this.getClient().mget(prefixedKeys) + if (Array.isArray(response)) { + let final: any = {} + let count = 0 + for (let result of response) { + if (result) { + let parsed + try { + parsed = JSON.parse(result) + } catch (err) { + parsed = result + } + final[keys[count]] = parsed + } + count++ + } + return final + } else { + throw new Error(`Invalid response: ${response}`) + } + } + + 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 this.getClient().set(prefixedKey, value) + if (expirySeconds) { + await this.getClient().expire(prefixedKey, expirySeconds) + } + } + + async getTTL(key: string) { + const db = this._db + const prefixedKey = addDbPrefix(db, key) + return this.getClient().ttl(prefixedKey) + } + + async setExpiry(key: string, expirySeconds: number | null) { + const db = this._db + const prefixedKey = addDbPrefix(db, key) + await this.getClient().expire(prefixedKey, expirySeconds) + } + + async delete(key: string) { + const db = this._db + await this.getClient().del(addDbPrefix(db, key)) + } + + async clear() { + let items = await this.scan() + await Promise.all(items.map((obj: any) => this.delete(obj.key))) + } +} + +export default RedisWrapper diff --git a/packages/backend-core/src/redis/redlock.ts b/packages/backend-core/src/redis/redlock.ts index beef375b55..54b2c0a8d1 100644 --- a/packages/backend-core/src/redis/redlock.ts +++ b/packages/backend-core/src/redis/redlock.ts @@ -1,14 +1,49 @@ -import Redlock from "redlock" +import Redlock, { Options } from "redlock" +import { getLockClient } from "./init" +import { LockOptions, LockType } from "@budibase/types" +import * as tenancy from "../tenancy" -export const getRedlock = (redisClient: any, opts = { retryCount: 10 }) => { - return new Redlock([redisClient], { +let noRetryRedlock: Redlock | undefined + +const getClient = async (type: LockType): Promise => { + switch (type) { + case LockType.TRY_ONCE: { + if (!noRetryRedlock) { + noRetryRedlock = await newRedlock(OPTIONS.TRY_ONCE) + } + return noRetryRedlock + } + case LockType.DEFAULT: { + if (!noRetryRedlock) { + noRetryRedlock = await newRedlock(OPTIONS.DEFAULT) + } + return noRetryRedlock + } + case LockType.DELAY_500: { + if (!noRetryRedlock) { + noRetryRedlock = await newRedlock(OPTIONS.DELAY_500) + } + return noRetryRedlock + } + default: { + throw new Error(`Could not get redlock client: ${type}`) + } + } +} + +export const OPTIONS = { + TRY_ONCE: { + // immediately throws an error if the lock is already held + retryCount: 0, + }, + DEFAULT: { // 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, + retryCount: 10, // the time in ms between attempts retryDelay: 200, // time in ms @@ -16,6 +51,51 @@ export const getRedlock = (redisClient: any, opts = { retryCount: 10 }) => { // 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 - }) + retryJitter: 100, // time in ms + }, + DELAY_500: { + retryDelay: 500, + }, +} + +export const newRedlock = async (opts: Options = {}) => { + let options = { ...OPTIONS.DEFAULT, ...opts } + const redisWrapper = await getLockClient() + const client = redisWrapper.getClient() + return new Redlock([client], options) +} + +export const doWithLock = async (opts: LockOptions, task: any) => { + const redlock = await getClient(opts.type) + let lock + try { + // aquire lock + let name: string = `lock:${tenancy.getTenantId()}_${opts.name}` + if (opts.nameSuffix) { + name = name + `_${opts.nameSuffix}` + } + lock = await redlock.lock(name, opts.ttl) + // perform locked task + // need to await to ensure completion before unlocking + const result = await task() + return result + } catch (e: any) { + console.log("lock error") + // lock limit exceeded + if (e.name === "LockError") { + if (opts.type === LockType.TRY_ONCE) { + // don't throw for try-once locks, they will always error + // due to retry count (0) exceeded + return + } else { + throw e + } + } else { + throw e + } + } finally { + if (lock) { + await lock.unlock() + } + } } diff --git a/packages/backend-core/src/redis/utils.js b/packages/backend-core/src/redis/utils.ts similarity index 68% rename from packages/backend-core/src/redis/utils.js rename to packages/backend-core/src/redis/utils.ts index 90b3561f31..4c556ebd54 100644 --- a/packages/backend-core/src/redis/utils.js +++ b/packages/backend-core/src/redis/utils.ts @@ -1,10 +1,10 @@ -const env = require("../environment") +import env from "../environment" const SLOT_REFRESH_MS = 2000 const CONNECT_TIMEOUT_MS = 10000 -const SEPARATOR = "-" const REDIS_URL = !env.REDIS_URL ? "localhost:6379" : env.REDIS_URL const REDIS_PASSWORD = !env.REDIS_PASSWORD ? "budibase" : env.REDIS_PASSWORD +export const SEPARATOR = "-" /** * These Redis databases help us to segment up a Redis keyspace by prepending the @@ -12,22 +12,23 @@ const REDIS_PASSWORD = !env.REDIS_PASSWORD ? "budibase" : env.REDIS_PASSWORD * 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. + * of the default keyspace and use a separate one - the SelectableDatabase can be used for this. */ -exports.Databases = { - PW_RESETS: "pwReset", - VERIFICATIONS: "verification", - INVITATIONS: "invitation", - DEV_LOCKS: "devLocks", - DEBOUNCE: "debounce", - SESSIONS: "session", - USER_CACHE: "users", - FLAGS: "flags", - APP_METADATA: "appMetadata", - QUERY_VARS: "queryVars", - LICENSES: "license", - GENERIC_CACHE: "data_cache", - WRITE_THROUGH: "writeThrough", +export enum Databases { + PW_RESETS = "pwReset", + VERIFICATIONS = "verification", + INVITATIONS = "invitation", + DEV_LOCKS = "devLocks", + DEBOUNCE = "debounce", + SESSIONS = "session", + USER_CACHE = "users", + FLAGS = "flags", + APP_METADATA = "appMetadata", + QUERY_VARS = "queryVars", + LICENSES = "license", + GENERIC_CACHE = "data_cache", + WRITE_THROUGH = "writeThrough", + LOCKS = "locks", } /** @@ -39,30 +40,28 @@ exports.Databases = { * 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, +export enum SelectableDatabase { + 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 - -exports.getRedisOptions = (clustered = false) => { +export function getRedisOptions(clustered = false) { let password = REDIS_PASSWORD - let url = REDIS_URL.split("//") + let url: string[] | string = REDIS_URL.split("//") // get rid of the protocol url = url.length > 1 ? url[1] : url[0] // check for a password etc @@ -83,7 +82,7 @@ exports.getRedisOptions = (clustered = false) => { redisProtocolUrl = REDIS_URL } - const opts = { + const opts: any = { connectTimeout: CONNECT_TIMEOUT_MS, } if (clustered) { @@ -91,7 +90,7 @@ exports.getRedisOptions = (clustered = false) => { opts.redisOptions.tls = {} opts.redisOptions.password = password opts.slotsRefreshTimeout = SLOT_REFRESH_MS - opts.dnsLookup = (address, callback) => callback(null, address) + opts.dnsLookup = (address: string, callback: any) => callback(null, address) } else { opts.host = host opts.port = port @@ -100,14 +99,14 @@ exports.getRedisOptions = (clustered = false) => { return { opts, host, port, redisProtocolUrl } } -exports.addDbPrefix = (db, key) => { +export function addDbPrefix(db: string, key: string) { if (key.includes(db)) { return key } return `${db}${SEPARATOR}${key}` } -exports.removeDbPrefix = key => { +export function removeDbPrefix(key: string) { let parts = key.split(SEPARATOR) if (parts.length >= 2) { parts.shift() diff --git a/packages/backend-core/src/security/apiKeys.js b/packages/backend-core/src/security/apiKeys.js deleted file mode 100644 index e90418abb8..0000000000 --- a/packages/backend-core/src/security/apiKeys.js +++ /dev/null @@ -1 +0,0 @@ -exports.lookupApiKey = async () => {} diff --git a/packages/backend-core/src/security/encryption.js b/packages/backend-core/src/security/encryption.js deleted file mode 100644 index c31f597652..0000000000 --- a/packages/backend-core/src/security/encryption.js +++ /dev/null @@ -1,33 +0,0 @@ -const crypto = require("crypto") -const env = require("../environment") - -const ALGO = "aes-256-ctr" -const SECRET = env.JWT_SECRET -const SEPARATOR = "-" -const ITERATIONS = 10000 -const RANDOM_BYTES = 16 -const STRETCH_LENGTH = 32 - -function stretchString(string, salt) { - return crypto.pbkdf2Sync(string, salt, ITERATIONS, STRETCH_LENGTH, "sha512") -} - -exports.encrypt = input => { - const salt = crypto.randomBytes(RANDOM_BYTES) - const stretched = stretchString(SECRET, salt) - const cipher = crypto.createCipheriv(ALGO, stretched, salt) - const base = cipher.update(input) - const final = cipher.final() - const encrypted = Buffer.concat([base, final]).toString("hex") - return `${salt.toString("hex")}${SEPARATOR}${encrypted}` -} - -exports.decrypt = input => { - const [salt, encrypted] = input.split(SEPARATOR) - const saltBuffer = Buffer.from(salt, "hex") - const stretched = stretchString(SECRET, saltBuffer) - const decipher = crypto.createDecipheriv(ALGO, stretched, saltBuffer) - const base = decipher.update(Buffer.from(encrypted, "hex")) - const final = decipher.final() - return Buffer.concat([base, final]).toString() -} diff --git a/packages/backend-core/src/security/encryption.ts b/packages/backend-core/src/security/encryption.ts new file mode 100644 index 0000000000..d0707cb850 --- /dev/null +++ b/packages/backend-core/src/security/encryption.ts @@ -0,0 +1,62 @@ +import crypto from "crypto" +import env from "../environment" + +const ALGO = "aes-256-ctr" +const SEPARATOR = "-" +const ITERATIONS = 10000 +const RANDOM_BYTES = 16 +const STRETCH_LENGTH = 32 + +export enum SecretOption { + JWT = "jwt", + ENCRYPTION = "encryption", +} + +function getSecret(secretOption: SecretOption): string { + let secret, secretName + switch (secretOption) { + case SecretOption.ENCRYPTION: + secret = env.ENCRYPTION_KEY + secretName = "ENCRYPTION_KEY" + break + case SecretOption.JWT: + default: + secret = env.JWT_SECRET + secretName = "JWT_SECRET" + break + } + if (!secret) { + throw new Error(`Secret "${secretName}" has not been set in environment.`) + } + return secret +} + +function stretchString(string: string, salt: Buffer) { + return crypto.pbkdf2Sync(string, salt, ITERATIONS, STRETCH_LENGTH, "sha512") +} + +export function encrypt( + input: string, + secretOption: SecretOption = SecretOption.JWT +) { + const salt = crypto.randomBytes(RANDOM_BYTES) + const stretched = stretchString(getSecret(secretOption), salt) + const cipher = crypto.createCipheriv(ALGO, stretched, salt) + const base = cipher.update(input) + const final = cipher.final() + const encrypted = Buffer.concat([base, final]).toString("hex") + return `${salt.toString("hex")}${SEPARATOR}${encrypted}` +} + +export function decrypt( + input: string, + secretOption: SecretOption = SecretOption.JWT +) { + const [salt, encrypted] = input.split(SEPARATOR) + const saltBuffer = Buffer.from(salt, "hex") + const stretched = stretchString(getSecret(secretOption), saltBuffer) + const decipher = crypto.createDecipheriv(ALGO, stretched, saltBuffer) + const base = decipher.update(Buffer.from(encrypted, "hex")) + const final = decipher.final() + return Buffer.concat([base, final]).toString() +} diff --git a/packages/backend-core/src/security/permissions.js b/packages/backend-core/src/security/permissions.js deleted file mode 100644 index 2ecb8a9f1e..0000000000 --- a/packages/backend-core/src/security/permissions.js +++ /dev/null @@ -1,164 +0,0 @@ -const { flatten } = require("lodash") -const { cloneDeep } = require("lodash/fp") - -const PermissionLevels = { - READ: "read", - WRITE: "write", - EXECUTE: "execute", - ADMIN: "admin", -} - -// these are the global types, that govern the underlying default behaviour -const PermissionTypes = { - APP: "app", - TABLE: "table", - USER: "user", - AUTOMATION: "automation", - WEBHOOK: "webhook", - BUILDER: "builder", - VIEW: "view", - QUERY: "query", -} - -function Permission(type, level) { - this.level = level - this.type = type -} - -function levelToNumber(perm) { - switch (perm) { - // not everything has execute privileges - case PermissionLevels.EXECUTE: - return 0 - case PermissionLevels.READ: - return 1 - case PermissionLevels.WRITE: - return 2 - case PermissionLevels.ADMIN: - return 3 - default: - return -1 - } -} - -/** - * Given the specified permission level for the user return the levels they are allowed to carry out. - * @param {string} userPermLevel The permission level of the user. - * @return {string[]} All the permission levels this user is allowed to carry out. - */ -function getAllowedLevels(userPermLevel) { - switch (userPermLevel) { - case PermissionLevels.EXECUTE: - return [PermissionLevels.EXECUTE] - case PermissionLevels.READ: - return [PermissionLevels.EXECUTE, PermissionLevels.READ] - case PermissionLevels.WRITE: - case PermissionLevels.ADMIN: - return [ - PermissionLevels.READ, - PermissionLevels.WRITE, - PermissionLevels.EXECUTE, - ] - default: - return [] - } -} - -exports.BUILTIN_PERMISSION_IDS = { - PUBLIC: "public", - READ_ONLY: "read_only", - WRITE: "write", - ADMIN: "admin", - POWER: "power", -} - -const BUILTIN_PERMISSIONS = { - PUBLIC: { - _id: exports.BUILTIN_PERMISSION_IDS.PUBLIC, - name: "Public", - permissions: [ - new Permission(PermissionTypes.WEBHOOK, PermissionLevels.EXECUTE), - ], - }, - READ_ONLY: { - _id: exports.BUILTIN_PERMISSION_IDS.READ_ONLY, - name: "Read only", - permissions: [ - new Permission(PermissionTypes.QUERY, PermissionLevels.READ), - new Permission(PermissionTypes.TABLE, PermissionLevels.READ), - new Permission(PermissionTypes.VIEW, PermissionLevels.READ), - ], - }, - WRITE: { - _id: exports.BUILTIN_PERMISSION_IDS.WRITE, - name: "Read/Write", - 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: { - _id: exports.BUILTIN_PERMISSION_IDS.POWER, - name: "Power", - permissions: [ - new Permission(PermissionTypes.TABLE, PermissionLevels.WRITE), - new Permission(PermissionTypes.USER, PermissionLevels.READ), - new Permission(PermissionTypes.AUTOMATION, PermissionLevels.EXECUTE), - new Permission(PermissionTypes.VIEW, PermissionLevels.READ), - new Permission(PermissionTypes.WEBHOOK, PermissionLevels.READ), - ], - }, - ADMIN: { - _id: exports.BUILTIN_PERMISSION_IDS.ADMIN, - name: "Admin", - permissions: [ - new Permission(PermissionTypes.TABLE, PermissionLevels.ADMIN), - new Permission(PermissionTypes.USER, PermissionLevels.ADMIN), - new Permission(PermissionTypes.AUTOMATION, PermissionLevels.ADMIN), - new Permission(PermissionTypes.VIEW, PermissionLevels.ADMIN), - new Permission(PermissionTypes.WEBHOOK, PermissionLevels.READ), - new Permission(PermissionTypes.QUERY, PermissionLevels.ADMIN), - ], - }, -} - -exports.getBuiltinPermissions = () => { - return cloneDeep(BUILTIN_PERMISSIONS) -} - -exports.getBuiltinPermissionByID = id => { - const perms = Object.values(BUILTIN_PERMISSIONS) - return perms.find(perm => perm._id === id) -} - -exports.doesHaveBasePermission = (permType, permLevel, rolesHierarchy) => { - const basePermissions = [ - ...new Set(rolesHierarchy.map(role => role.permissionId)), - ] - const builtins = Object.values(BUILTIN_PERMISSIONS) - let permissions = flatten( - builtins - .filter(builtin => basePermissions.indexOf(builtin._id) !== -1) - .map(builtin => builtin.permissions) - ) - for (let permission of permissions) { - if ( - permission.type === permType && - getAllowedLevels(permission.level).indexOf(permLevel) !== -1 - ) { - return true - } - } - return false -} - -exports.isPermissionLevelHigherThanRead = level => { - return levelToNumber(level) > 1 -} - -// utility as a lot of things need simply the builder permission -exports.BUILDER = PermissionTypes.BUILDER -exports.PermissionTypes = PermissionTypes -exports.PermissionLevels = PermissionLevels diff --git a/packages/backend-core/src/security/permissions.ts b/packages/backend-core/src/security/permissions.ts new file mode 100644 index 0000000000..42189bba0c --- /dev/null +++ b/packages/backend-core/src/security/permissions.ts @@ -0,0 +1,175 @@ +const { flatten } = require("lodash") +const { cloneDeep } = require("lodash/fp") + +export type RoleHierarchy = { + permissionId: string +}[] + +export enum PermissionLevel { + READ = "read", + WRITE = "write", + EXECUTE = "execute", + ADMIN = "admin", +} + +// these are the global types, that govern the underlying default behaviour +export enum PermissionType { + APP = "app", + TABLE = "table", + USER = "user", + AUTOMATION = "automation", + WEBHOOK = "webhook", + BUILDER = "builder", + VIEW = "view", + QUERY = "query", +} + +class Permission { + type: PermissionType + level: PermissionLevel + + constructor(type: PermissionType, level: PermissionLevel) { + this.type = type + this.level = level + } +} + +function levelToNumber(perm: PermissionLevel) { + switch (perm) { + // not everything has execute privileges + case PermissionLevel.EXECUTE: + return 0 + case PermissionLevel.READ: + return 1 + case PermissionLevel.WRITE: + return 2 + case PermissionLevel.ADMIN: + return 3 + default: + return -1 + } +} + +/** + * Given the specified permission level for the user return the levels they are allowed to carry out. + * @param {string} userPermLevel The permission level of the user. + * @return {string[]} All the permission levels this user is allowed to carry out. + */ +function getAllowedLevels(userPermLevel: PermissionLevel) { + switch (userPermLevel) { + case PermissionLevel.EXECUTE: + return [PermissionLevel.EXECUTE] + case PermissionLevel.READ: + return [PermissionLevel.EXECUTE, PermissionLevel.READ] + case PermissionLevel.WRITE: + case PermissionLevel.ADMIN: + return [ + PermissionLevel.READ, + PermissionLevel.WRITE, + PermissionLevel.EXECUTE, + ] + default: + return [] + } +} + +export enum BuiltinPermissionID { + PUBLIC = "public", + READ_ONLY = "read_only", + WRITE = "write", + ADMIN = "admin", + POWER = "power", +} + +const BUILTIN_PERMISSIONS = { + PUBLIC: { + _id: BuiltinPermissionID.PUBLIC, + name: "Public", + permissions: [ + new Permission(PermissionType.WEBHOOK, PermissionLevel.EXECUTE), + ], + }, + READ_ONLY: { + _id: BuiltinPermissionID.READ_ONLY, + name: "Read only", + permissions: [ + new Permission(PermissionType.QUERY, PermissionLevel.READ), + new Permission(PermissionType.TABLE, PermissionLevel.READ), + new Permission(PermissionType.VIEW, PermissionLevel.READ), + ], + }, + WRITE: { + _id: BuiltinPermissionID.WRITE, + name: "Read/Write", + permissions: [ + new Permission(PermissionType.QUERY, PermissionLevel.WRITE), + new Permission(PermissionType.TABLE, PermissionLevel.WRITE), + new Permission(PermissionType.VIEW, PermissionLevel.READ), + new Permission(PermissionType.AUTOMATION, PermissionLevel.EXECUTE), + ], + }, + POWER: { + _id: BuiltinPermissionID.POWER, + name: "Power", + permissions: [ + new Permission(PermissionType.TABLE, PermissionLevel.WRITE), + new Permission(PermissionType.USER, PermissionLevel.READ), + new Permission(PermissionType.AUTOMATION, PermissionLevel.EXECUTE), + new Permission(PermissionType.VIEW, PermissionLevel.READ), + new Permission(PermissionType.WEBHOOK, PermissionLevel.READ), + ], + }, + ADMIN: { + _id: BuiltinPermissionID.ADMIN, + name: "Admin", + permissions: [ + new Permission(PermissionType.TABLE, PermissionLevel.ADMIN), + new Permission(PermissionType.USER, PermissionLevel.ADMIN), + new Permission(PermissionType.AUTOMATION, PermissionLevel.ADMIN), + new Permission(PermissionType.VIEW, PermissionLevel.ADMIN), + new Permission(PermissionType.WEBHOOK, PermissionLevel.READ), + new Permission(PermissionType.QUERY, PermissionLevel.ADMIN), + ], + }, +} + +export function getBuiltinPermissions() { + return cloneDeep(BUILTIN_PERMISSIONS) +} + +export function getBuiltinPermissionByID(id: string) { + const perms = Object.values(BUILTIN_PERMISSIONS) + return perms.find(perm => perm._id === id) +} + +export function doesHaveBasePermission( + permType: PermissionType, + permLevel: PermissionLevel, + rolesHierarchy: RoleHierarchy +) { + const basePermissions = [ + ...new Set(rolesHierarchy.map(role => role.permissionId)), + ] + const builtins = Object.values(BUILTIN_PERMISSIONS) + let permissions = flatten( + builtins + .filter(builtin => basePermissions.indexOf(builtin._id) !== -1) + .map(builtin => builtin.permissions) + ) + for (let permission of permissions) { + if ( + permission.type === permType && + getAllowedLevels(permission.level).indexOf(permLevel) !== -1 + ) { + return true + } + } + return false +} + +export function isPermissionLevelHigherThanRead(level: PermissionLevel) { + return levelToNumber(level) > 1 +} + +// utility as a lot of things need simply the builder permission +export const BUILDER = PermissionType.BUILDER diff --git a/packages/backend-core/src/security/roles.js b/packages/backend-core/src/security/roles.ts similarity index 60% rename from packages/backend-core/src/security/roles.js rename to packages/backend-core/src/security/roles.ts index 33c9123b63..bdf7a38726 100644 --- a/packages/backend-core/src/security/roles.js +++ b/packages/backend-core/src/security/roles.ts @@ -1,21 +1,22 @@ +import { BuiltinPermissionID, PermissionLevel } from "./permissions" +import { generateRoleID, getRoleParams, DocumentType, SEPARATOR } from "../db" +import { getAppDB } from "../context" +import { doWithDB } from "../db" +import { Screen, Role as RoleDoc } from "@budibase/types" const { cloneDeep } = require("lodash/fp") -const { BUILTIN_PERMISSION_IDS, PermissionLevels } = require("./permissions") -const { - generateRoleID, - getRoleParams, - DocumentType, - SEPARATOR, -} = require("../db/utils") -const { getAppDB } = require("../context") -const { doWithDB } = require("../db") -const BUILTIN_IDS = { +export const BUILTIN_ROLE_IDS = { ADMIN: "ADMIN", POWER: "POWER", BASIC: "BASIC", PUBLIC: "PUBLIC", } +const BUILTIN_IDS = { + ...BUILTIN_ROLE_IDS, + BUILDER: "BUILDER", +} + // exclude internal roles like builder const EXTERNAL_BUILTIN_ROLE_IDS = [ BUILTIN_IDS.ADMIN, @@ -24,60 +25,70 @@ const EXTERNAL_BUILTIN_ROLE_IDS = [ BUILTIN_IDS.PUBLIC, ] -function Role(id, name) { - this._id = id - this.name = name -} +export class Role implements RoleDoc { + _id: string + _rev?: string + name: string + permissionId: string + inherits?: string + permissions = {} -Role.prototype.addPermission = function (permissionId) { - this.permissionId = permissionId - return this -} + constructor(id: string, name: string, permissionId: string) { + this._id = id + this.name = name + this.permissionId = permissionId + } -Role.prototype.addInheritance = function (inherits) { - this.inherits = inherits - return this + addInheritance(inherits: string) { + this.inherits = inherits + return this + } } const BUILTIN_ROLES = { - ADMIN: new Role(BUILTIN_IDS.ADMIN, "Admin") - .addPermission(BUILTIN_PERMISSION_IDS.ADMIN) - .addInheritance(BUILTIN_IDS.POWER), - POWER: new Role(BUILTIN_IDS.POWER, "Power") - .addPermission(BUILTIN_PERMISSION_IDS.POWER) - .addInheritance(BUILTIN_IDS.BASIC), - BASIC: new Role(BUILTIN_IDS.BASIC, "Basic") - .addPermission(BUILTIN_PERMISSION_IDS.WRITE) - .addInheritance(BUILTIN_IDS.PUBLIC), - PUBLIC: new Role(BUILTIN_IDS.PUBLIC, "Public").addPermission( - BUILTIN_PERMISSION_IDS.PUBLIC - ), - BUILDER: new Role(BUILTIN_IDS.BUILDER, "Builder").addPermission( - BUILTIN_PERMISSION_IDS.ADMIN - ), + ADMIN: new Role( + BUILTIN_IDS.ADMIN, + "Admin", + BuiltinPermissionID.ADMIN + ).addInheritance(BUILTIN_IDS.POWER), + POWER: new Role( + BUILTIN_IDS.POWER, + "Power", + BuiltinPermissionID.POWER + ).addInheritance(BUILTIN_IDS.BASIC), + BASIC: new Role( + BUILTIN_IDS.BASIC, + "Basic", + BuiltinPermissionID.WRITE + ).addInheritance(BUILTIN_IDS.PUBLIC), + PUBLIC: new Role(BUILTIN_IDS.PUBLIC, "Public", BuiltinPermissionID.PUBLIC), + BUILDER: new Role(BUILTIN_IDS.BUILDER, "Builder", BuiltinPermissionID.ADMIN), } -exports.getBuiltinRoles = () => { +export function getBuiltinRoles(): { [key: string]: RoleDoc } { return cloneDeep(BUILTIN_ROLES) } -exports.BUILTIN_ROLE_ID_ARRAY = Object.values(BUILTIN_ROLES).map( +export const BUILTIN_ROLE_ID_ARRAY = Object.values(BUILTIN_ROLES).map( role => role._id ) -exports.BUILTIN_ROLE_NAME_ARRAY = Object.values(BUILTIN_ROLES).map( +export const BUILTIN_ROLE_NAME_ARRAY = Object.values(BUILTIN_ROLES).map( role => role.name ) -function isBuiltin(role) { - return exports.BUILTIN_ROLE_ID_ARRAY.some(builtin => role.includes(builtin)) +export function isBuiltin(role?: string) { + return BUILTIN_ROLE_ID_ARRAY.some(builtin => role?.includes(builtin)) } /** * Works through the inheritance ranks to see how far up the builtin stack this ID is. */ -exports.builtinRoleToNumber = id => { - const builtins = exports.getBuiltinRoles() +export function builtinRoleToNumber(id?: string) { + if (!id) { + return 0 + } + const builtins = getBuiltinRoles() const MAX = Object.values(builtins).length + 1 if (id === BUILTIN_IDS.ADMIN || id === BUILTIN_IDS.BUILDER) { return MAX @@ -88,7 +99,7 @@ exports.builtinRoleToNumber = id => { if (!role) { break } - role = builtins[role.inherits] + role = builtins[role.inherits!] count++ } while (role !== null) return count @@ -97,14 +108,14 @@ exports.builtinRoleToNumber = id => { /** * Converts any role to a number, but has to be async to get the roles from db. */ -exports.roleToNumber = async id => { - if (exports.isBuiltin(id)) { - return exports.builtinRoleToNumber(id) +export async function roleToNumber(id?: string) { + if (isBuiltin(id)) { + return builtinRoleToNumber(id) } - const hierarchy = await exports.getUserRoleHierarchy(id) + const hierarchy = (await getUserRoleHierarchy(id)) as RoleDoc[] for (let role of hierarchy) { - if (isBuiltin(role.inherits)) { - return exports.builtinRoleToNumber(role.inherits) + 1 + if (isBuiltin(role?.inherits)) { + return builtinRoleToNumber(role.inherits) + 1 } } return 0 @@ -113,15 +124,14 @@ exports.roleToNumber = async id => { /** * Returns whichever builtin roleID is lower. */ -exports.lowerBuiltinRoleID = (roleId1, roleId2) => { +export function lowerBuiltinRoleID(roleId1?: string, roleId2?: string): string { if (!roleId1) { - return roleId2 + return roleId2 as string } if (!roleId2) { - return roleId1 + return roleId1 as string } - return exports.builtinRoleToNumber(roleId1) > - exports.builtinRoleToNumber(roleId2) + return builtinRoleToNumber(roleId1) > builtinRoleToNumber(roleId2) ? roleId2 : roleId1 } @@ -132,11 +142,11 @@ exports.lowerBuiltinRoleID = (roleId1, roleId2) => { * @param {string|null} roleId The level ID to lookup. * @returns {Promise} The role object, which may contain an "inherits" property. */ -exports.getRole = async roleId => { +export async function getRole(roleId?: string): Promise { if (!roleId) { - return null + return undefined } - let role = {} + let role: any = {} // built in roles mostly come from the in-code implementation, // but can be extended by a doc stored about them (e.g. permissions) if (isBuiltin(roleId)) { @@ -146,10 +156,10 @@ exports.getRole = async roleId => { } try { const db = getAppDB() - const dbRole = await db.get(exports.getDBRoleID(roleId)) + const dbRole = await db.get(getDBRoleID(roleId)) role = Object.assign(role, dbRole) // finalise the ID - role._id = exports.getExternalRoleID(role._id) + role._id = getExternalRoleID(role._id) } catch (err) { // only throw an error if there is no role at all if (Object.keys(role).length === 0) { @@ -162,12 +172,12 @@ exports.getRole = async roleId => { /** * Simple function to get all the roles based on the top level user role ID. */ -async function getAllUserRoles(userRoleId) { +async function getAllUserRoles(userRoleId?: string): Promise { // admins have access to all roles if (userRoleId === BUILTIN_IDS.ADMIN) { - return exports.getAllRoles() + return getAllRoles() } - let currentRole = await exports.getRole(userRoleId) + let currentRole = await getRole(userRoleId) let roles = currentRole ? [currentRole] : [] let roleIds = [userRoleId] // get all the inherited roles @@ -177,8 +187,10 @@ async function getAllUserRoles(userRoleId) { roleIds.indexOf(currentRole.inherits) === -1 ) { roleIds.push(currentRole.inherits) - currentRole = await exports.getRole(currentRole.inherits) - roles.push(currentRole) + currentRole = await getRole(currentRole.inherits) + if (currentRole) { + roles.push(currentRole) + } } return roles } @@ -191,7 +203,10 @@ async function getAllUserRoles(userRoleId) { * @returns {Promise} returns an ordered array of the roles, with the first being their * highest level of access and the last being the lowest level. */ -exports.getUserRoleHierarchy = async (userRoleId, opts = { idOnly: true }) => { +export async function getUserRoleHierarchy( + userRoleId?: string, + opts = { idOnly: true } +) { // special case, if they don't have a role then they are a public user const roles = await getAllUserRoles(userRoleId) return opts.idOnly ? roles.map(role => role._id) : roles @@ -200,12 +215,15 @@ exports.getUserRoleHierarchy = async (userRoleId, opts = { idOnly: true }) => { // this function checks that the provided permissions are in an array format // some templates/older apps will use a simple string instead of array for roles // convert the string to an array using the theory that write is higher than read -exports.checkForRoleResourceArray = (rolePerms, resourceId) => { +export function checkForRoleResourceArray( + rolePerms: { [key: string]: string[] }, + resourceId: string +) { if (rolePerms && !Array.isArray(rolePerms[resourceId])) { - const permLevel = rolePerms[resourceId] + const permLevel = rolePerms[resourceId] as any rolePerms[resourceId] = [permLevel] - if (permLevel === PermissionLevels.WRITE) { - rolePerms[resourceId].push(PermissionLevels.READ) + if (permLevel === PermissionLevel.WRITE) { + rolePerms[resourceId].push(PermissionLevel.READ) } } return rolePerms @@ -215,7 +233,7 @@ exports.checkForRoleResourceArray = (rolePerms, resourceId) => { * Given an app ID this will retrieve all of the roles that are currently within that app. * @return {Promise} An array of the role objects that were found. */ -exports.getAllRoles = async appId => { +export async function getAllRoles(appId?: string) { if (appId) { return doWithDB(appId, internal) } else { @@ -227,30 +245,30 @@ exports.getAllRoles = async appId => { } return internal(appDB) } - async function internal(db) { - let roles = [] + async function internal(db: any) { + let roles: RoleDoc[] = [] if (db) { const body = await db.allDocs( getRoleParams(null, { include_docs: true, }) ) - roles = body.rows.map(row => row.doc) + roles = body.rows.map((row: any) => row.doc) } - const builtinRoles = exports.getBuiltinRoles() + const builtinRoles = 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 + dbRole => 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) + dbBuiltin._id = getExternalRoleID(dbBuiltin._id) roles.push(Object.assign(builtinRole, dbBuiltin)) } } @@ -260,7 +278,7 @@ exports.getAllRoles = async appId => { continue } for (let resourceId of Object.keys(role.permissions)) { - role.permissions = exports.checkForRoleResourceArray( + role.permissions = checkForRoleResourceArray( role.permissions, resourceId ) @@ -277,11 +295,11 @@ exports.getAllRoles = async appId => { * @param subResourceId The sub resource being requested * @return {Promise<{permissions}|Object>} returns the permissions required to access. */ -exports.getRequiredResourceRole = async ( - permLevel, - { resourceId, subResourceId } -) => { - const roles = await exports.getAllRoles() +export async function getRequiredResourceRole( + permLevel: string, + { resourceId, subResourceId }: { resourceId?: string; subResourceId?: string } +) { + const roles = await getAllRoles() let main = [], sub = [] for (let role of roles) { @@ -289,8 +307,8 @@ exports.getRequiredResourceRole = async ( if (!role.permissions) { continue } - const mainRes = role.permissions[resourceId] - const subRes = role.permissions[subResourceId] + const mainRes = resourceId ? role.permissions[resourceId] : undefined + const subRes = subResourceId ? role.permissions[subResourceId] : undefined if (mainRes && mainRes.indexOf(permLevel) !== -1) { main.push(role._id) } else if (subRes && subRes.indexOf(permLevel) !== -1) { @@ -301,12 +319,13 @@ exports.getRequiredResourceRole = async ( return main.concat(sub) } -class AccessController { +export class AccessController { + userHierarchies: { [key: string]: string[] } constructor() { this.userHierarchies = {} } - async hasAccess(tryingRoleId, userRoleId) { + async hasAccess(tryingRoleId?: string, userRoleId?: string) { // special cases, the screen has no role, the roles are the same or the user // is currently in the builder if ( @@ -318,16 +337,18 @@ class AccessController { ) { return true } - let roleIds = this.userHierarchies[userRoleId] - if (!roleIds) { - roleIds = await exports.getUserRoleHierarchy(userRoleId) + let roleIds = userRoleId ? this.userHierarchies[userRoleId] : null + if (!roleIds && userRoleId) { + roleIds = (await getUserRoleHierarchy(userRoleId, { + idOnly: true, + })) as string[] this.userHierarchies[userRoleId] = roleIds } - return roleIds.indexOf(tryingRoleId) !== -1 + return roleIds?.indexOf(tryingRoleId) !== -1 } - async checkScreensAccess(screens, userRoleId) { + async checkScreensAccess(screens: Screen[], userRoleId: string) { let accessibleScreens = [] // don't want to handle this with Promise.all as this would mean all custom roles would be // retrieved at same time, it is likely a custom role will be re-used and therefore want @@ -341,8 +362,8 @@ class AccessController { return accessibleScreens } - async checkScreenAccess(screen, userRoleId) { - const roleId = screen && screen.routing ? screen.routing.roleId : null + async checkScreenAccess(screen: Screen, userRoleId: string) { + const roleId = screen && screen.routing ? screen.routing.roleId : undefined if (await this.hasAccess(roleId, userRoleId)) { return screen } @@ -353,8 +374,8 @@ class AccessController { /** * Adds the "role_" for builtin role IDs which are to be written to the DB (for permissions). */ -exports.getDBRoleID = roleId => { - if (roleId.startsWith(DocumentType.ROLE)) { +export function getDBRoleID(roleId?: string) { + if (roleId?.startsWith(DocumentType.ROLE)) { return roleId } return generateRoleID(roleId) @@ -363,15 +384,10 @@ exports.getDBRoleID = roleId => { /** * Remove the "role_" from builtin role IDs that have been written to the DB (for permissions). */ -exports.getExternalRoleID = roleId => { - // for built in roles we want to remove the DB role ID element (role_) - if (roleId.startsWith(DocumentType.ROLE) && isBuiltin(roleId)) { +export function getExternalRoleID(roleId?: string) { + // for built-in roles we want to remove the DB role ID element (role_) + if (roleId?.startsWith(DocumentType.ROLE) && isBuiltin(roleId)) { return roleId.split(`${DocumentType.ROLE}${SEPARATOR}`)[1] } return roleId } - -exports.AccessController = AccessController -exports.BUILTIN_ROLE_IDS = BUILTIN_IDS -exports.isBuiltin = isBuiltin -exports.Role = Role diff --git a/packages/backend-core/src/security/sessions.ts b/packages/backend-core/src/security/sessions.ts index 33230afc60..5a535c0c46 100644 --- a/packages/backend-core/src/security/sessions.ts +++ b/packages/backend-core/src/security/sessions.ts @@ -1,7 +1,7 @@ const redis = require("../redis/init") const { v4: uuidv4 } = require("uuid") const { logWarn } = require("../logging") -const env = require("../environment") +import env from "../environment" import { Session, ScannedSession, @@ -89,6 +89,7 @@ export async function createASession( userId, } await client.store(key, session, EXPIRY_SECONDS) + return session } export async function updateSessionTTL(session: Session) { diff --git a/packages/backend-core/src/tenancy/index.ts b/packages/backend-core/src/tenancy/index.ts index e0006abab2..1618a136dd 100644 --- a/packages/backend-core/src/tenancy/index.ts +++ b/packages/backend-core/src/tenancy/index.ts @@ -1,9 +1,2 @@ -import * as context from "../context" -import * as tenancy from "./tenancy" - -const pkg = { - ...context, - ...tenancy, -} - -export = pkg +export * from "../context" +export * from "./tenancy" diff --git a/packages/backend-core/src/tenancy/tenancy.ts b/packages/backend-core/src/tenancy/tenancy.ts index ad5c6b5287..732402bcb7 100644 --- a/packages/backend-core/src/tenancy/tenancy.ts +++ b/packages/backend-core/src/tenancy/tenancy.ts @@ -1,20 +1,22 @@ -import { doWithDB } from "../db" -import { queryPlatformView } from "../db/views" -import { StaticDatabases, ViewName } from "../db/constants" -import { getGlobalDBName } from "../db/tenancy" +import { doWithDB, getGlobalDBName } from "../db" import { - getTenantId, DEFAULT_TENANT_ID, - isMultiTenant, + getTenantId, getTenantIDFromAppID, + isMultiTenant, } from "../context" import env from "../environment" -import { PlatformUser } from "@budibase/types" +import { + BBContext, + TenantResolutionStrategy, + GetTenantIdOptions, +} from "@budibase/types" +import { Header, StaticDatabases } from "../constants" const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name -export const addTenantToUrl = (url: string) => { +export function addTenantToUrl(url: string) { const tenantId = getTenantId() if (isMultiTenant()) { @@ -25,7 +27,7 @@ export const addTenantToUrl = (url: string) => { return url } -export const doesTenantExist = async (tenantId: string) => { +export async function doesTenantExist(tenantId: string) { return doWithDB(PLATFORM_INFO_DB, async (db: any) => { let tenants try { @@ -42,12 +44,12 @@ export const doesTenantExist = async (tenantId: string) => { }) } -export const tryAddTenant = async ( +export async function tryAddTenant( tenantId: string, userId: string, email: string, afterCreateTenant: () => Promise -) => { +) { return doWithDB(PLATFORM_INFO_DB, async (db: any) => { const getDoc = async (id: string) => { if (!id) { @@ -89,11 +91,11 @@ export const tryAddTenant = async ( }) } -export const doWithGlobalDB = (tenantId: string, cb: any) => { +export function doWithGlobalDB(tenantId: string, cb: any) { return doWithDB(getGlobalDBName(tenantId), cb) } -export const lookupTenantId = async (userId: string) => { +export async function lookupTenantId(userId: string) { return doWithDB(StaticDatabases.PLATFORM_INFO.name, async (db: any) => { let tenantId = env.MULTI_TENANCY ? DEFAULT_TENANT_ID : null try { @@ -108,19 +110,6 @@ export const lookupTenantId = async (userId: string) => { }) } -// lookup, could be email or userId, either will return a doc -export const getTenantUser = async ( - identifier: string -): Promise => { - // use the view here and allow to find anyone regardless of casing - // Use lowercase to ensure email login is case insensitive - const response = queryPlatformView(ViewName.PLATFORM_USERS_LOWERCASE, { - keys: [identifier.toLowerCase()], - include_docs: true, - }) as Promise - return response -} - export const isUserInAppTenant = (appId: string, user?: any) => { let userTenantId if (user) { @@ -132,7 +121,7 @@ export const isUserInAppTenant = (appId: string, user?: any) => { return tenantId === userTenantId } -export const getTenantIds = async () => { +export async function getTenantIds() { return doWithDB(PLATFORM_INFO_DB, async (db: any) => { let tenants try { @@ -144,3 +133,108 @@ export const getTenantIds = async () => { return (tenants && tenants.tenantIds) || [] }) } + +const ALL_STRATEGIES = Object.values(TenantResolutionStrategy) + +export const getTenantIDFromCtx = ( + ctx: BBContext, + opts: GetTenantIdOptions +): string | null => { + // exit early if not multi-tenant + if (!isMultiTenant()) { + return DEFAULT_TENANT_ID + } + + // opt defaults + if (opts.allowNoTenant === undefined) { + opts.allowNoTenant = false + } + if (!opts.includeStrategies) { + opts.includeStrategies = ALL_STRATEGIES + } + if (!opts.excludeStrategies) { + opts.excludeStrategies = [] + } + + const isAllowed = (strategy: TenantResolutionStrategy) => { + // excluded takes precedence + if (opts.excludeStrategies?.includes(strategy)) { + return false + } + if (opts.includeStrategies?.includes(strategy)) { + return true + } + } + + // always use user first + if (isAllowed(TenantResolutionStrategy.USER)) { + const userTenantId = ctx.user?.tenantId + if (userTenantId) { + return userTenantId + } + } + + // header + if (isAllowed(TenantResolutionStrategy.HEADER)) { + const headerTenantId = ctx.request.headers[Header.TENANT_ID] + if (headerTenantId) { + return headerTenantId as string + } + } + + // query param + if (isAllowed(TenantResolutionStrategy.QUERY)) { + const queryTenantId = ctx.request.query.tenantId + if (queryTenantId) { + return queryTenantId as string + } + } + + // subdomain + if (isAllowed(TenantResolutionStrategy.SUBDOMAIN)) { + // e.g. budibase.app or local.com:10000 + const platformHost = new URL(env.PLATFORM_URL).host.split(":")[0] + // e.g. tenant.budibase.app or tenant.local.com + const requestHost = ctx.host + // parse the tenant id from the difference + if (requestHost.includes(platformHost)) { + const tenantId = requestHost.substring( + 0, + requestHost.indexOf(`.${platformHost}`) + ) + if (tenantId) { + return tenantId + } + } + } + + // path + if (isAllowed(TenantResolutionStrategy.PATH)) { + // params - have to parse manually due to koa-router not run yet + const match = ctx.matched.find( + (m: any) => !!m.paramNames.find((p: any) => p.name === "tenantId") + ) + + // get the raw path url - without any query params + const ctxUrl = ctx.originalUrl + let url + if (ctxUrl.includes("?")) { + url = ctxUrl.split("?")[0] + } else { + url = ctxUrl + } + + if (match) { + const params = match.params(url, match.captures(url), {}) + if (params.tenantId) { + return params.tenantId + } + } + } + + if (!opts.allowNoTenant) { + ctx.throw(403, "Tenant id not set") + } + + return null +} diff --git a/packages/backend-core/src/tests/utils.spec.js b/packages/backend-core/src/tests/utils.spec.js deleted file mode 100644 index b487f23c6d..0000000000 --- a/packages/backend-core/src/tests/utils.spec.js +++ /dev/null @@ -1,17 +0,0 @@ -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.ts b/packages/backend-core/src/users.ts index 44f04749c9..1720a79a83 100644 --- a/packages/backend-core/src/users.ts +++ b/packages/backend-core/src/users.ts @@ -3,15 +3,14 @@ import { getUsersByAppParams, getProdAppID, generateAppUserID, -} from "./db/utils" -import { queryGlobalView } from "./db/views" -import { UNICODE_MAX } from "./db/constants" + queryGlobalView, + UNICODE_MAX, +} from "./db" import { BulkDocsResponse, User } from "@budibase/types" import { getGlobalDB } from "./context" -import PouchDB from "pouchdb" export const bulkGetGlobalUsersById = async (userIds: string[]) => { - const db = getGlobalDB() as PouchDB.Database + const db = getGlobalDB() return ( await db.allDocs({ keys: userIds, @@ -21,7 +20,7 @@ export const bulkGetGlobalUsersById = async (userIds: string[]) => { } export const bulkUpdateGlobalUsers = async (users: User[]) => { - const db = getGlobalDB() as PouchDB.Database + const db = getGlobalDB() return (await db.bulkDocs(users)) as BulkDocsResponse } @@ -69,7 +68,7 @@ export const getGlobalUserByAppPage = (appId: string, user: User) => { if (!user) { return } - return generateAppUserID(getProdAppID(appId), user._id!) + return generateAppUserID(getProdAppID(appId)!, user._id!) } /** diff --git a/packages/backend-core/src/hashing.js b/packages/backend-core/src/utils/hashing.ts similarity index 53% rename from packages/backend-core/src/hashing.js rename to packages/backend-core/src/utils/hashing.ts index 7524e66043..220ffea47f 100644 --- a/packages/backend-core/src/hashing.js +++ b/packages/backend-core/src/utils/hashing.ts @@ -1,18 +1,14 @@ -const env = require("./environment") +import env from "../environment" +export * from "../newid" const bcrypt = env.JS_BCRYPT ? require("bcryptjs") : require("bcrypt") -const { v4 } = require("uuid") const SALT_ROUNDS = env.SALT_ROUNDS || 10 -exports.hash = async data => { +export async function hash(data: string) { const salt = await bcrypt.genSalt(SALT_ROUNDS) return bcrypt.hash(data, salt) } -exports.compare = async (data, encrypted) => { +export async function compare(data: string, encrypted: string) { return bcrypt.compare(data, encrypted) } - -exports.newid = function () { - return v4().replace(/-/g, "") -} diff --git a/packages/backend-core/src/utils/index.ts b/packages/backend-core/src/utils/index.ts new file mode 100644 index 0000000000..8e663bce52 --- /dev/null +++ b/packages/backend-core/src/utils/index.ts @@ -0,0 +1,2 @@ +export * from "./hashing" +export * from "./utils" diff --git a/packages/backend-core/src/utils/tests/utils.spec.ts b/packages/backend-core/src/utils/tests/utils.spec.ts new file mode 100644 index 0000000000..498aff1555 --- /dev/null +++ b/packages/backend-core/src/utils/tests/utils.spec.ts @@ -0,0 +1,109 @@ +import { structures } from "../../../tests" +import * as utils from "../../utils" +import * as events from "../../events" +import * as db from "../../db" +import { DEFAULT_TENANT_ID, Header } from "../../constants" +import { doInTenant } from "../../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) + }) + }) + }) + + describe("getAppIdFromCtx", () => { + it("gets appId from header", async () => { + const ctx = structures.koa.newContext() + const expected = db.generateAppID() + ctx.request.headers = { + [Header.APP_ID]: expected, + } + + const actual = await utils.getAppIdFromCtx(ctx) + expect(actual).toBe(expected) + }) + + it("gets appId from body", async () => { + const ctx = structures.koa.newContext() + const expected = db.generateAppID() + ctx.request.body = { + appId: expected, + } + + const actual = await utils.getAppIdFromCtx(ctx) + expect(actual).toBe(expected) + }) + + it("gets appId from path", async () => { + const ctx = structures.koa.newContext() + const expected = db.generateAppID() + ctx.path = `/apps/${expected}` + + const actual = await utils.getAppIdFromCtx(ctx) + expect(actual).toBe(expected) + }) + + it("gets appId from url", async () => { + const ctx = structures.koa.newContext() + const expected = db.generateAppID() + const app = structures.apps.app(expected) + + // set custom url + const appUrl = "custom-url" + app.url = `/${appUrl}` + ctx.path = `/app/${appUrl}` + + // save the app + const database = db.getDB(expected) + await database.put(app) + + const actual = await utils.getAppIdFromCtx(ctx) + expect(actual).toBe(expected) + }) + + it("doesn't get appId from url when previewing", async () => { + const ctx = structures.koa.newContext() + const appId = db.generateAppID() + const app = structures.apps.app(appId) + + // set custom url + const appUrl = "preview" + app.url = `/${appUrl}` + ctx.path = `/app/${appUrl}` + + // save the app + const database = db.getDB(appId) + await database.put(app) + + const actual = await utils.getAppIdFromCtx(ctx) + expect(actual).toBe(undefined) + }) + + it("gets appId from referer", async () => { + const ctx = structures.koa.newContext() + const expected = db.generateAppID() + ctx.request.headers = { + referer: `http://test.com/builder/app/${expected}/design/screen_123/screens`, + } + + const actual = await utils.getAppIdFromCtx(ctx) + expect(actual).toBe(expected) + }) + + it("doesn't get appId from referer when not builder", async () => { + const ctx = structures.koa.newContext() + const appId = db.generateAppID() + ctx.request.headers = { + referer: `http://test.com/foo/app/${appId}/bar`, + } + + const actual = await utils.getAppIdFromCtx(ctx) + expect(actual).toBe(undefined) + }) + }) +}) diff --git a/packages/backend-core/src/utils.js b/packages/backend-core/src/utils/utils.ts similarity index 52% rename from packages/backend-core/src/utils.js rename to packages/backend-core/src/utils/utils.ts index 6b59c7cb72..c608686431 100644 --- a/packages/backend-core/src/utils.js +++ b/packages/backend-core/src/utils/utils.ts @@ -1,38 +1,55 @@ -const { DocumentType, SEPARATOR, ViewName, getAllApps } = require("./db/utils") +import { getAllApps, queryGlobalView } from "../db" +import { options } from "../middleware/passport/jwt" +import { + Header, + Cookie, + MAX_VALID_DATE, + DocumentType, + SEPARATOR, + ViewName, +} from "../constants" +import env from "../environment" +import * as userCache from "../cache/user" +import { getSessionsForUser, invalidateSessions } from "../security/sessions" +import * as events from "../events" +import * as tenancy from "../tenancy" +import { + App, + Ctx, + PlatformLogoutOpts, + TenantResolutionStrategy, +} from "@budibase/types" +import { SetOption } from "cookies" const jwt = require("jsonwebtoken") -const { options } = require("./middleware/passport/jwt") -const { queryGlobalView } = require("./db/views") -const { Headers, Cookies, MAX_VALID_DATE } = require("./constants") -const env = require("./environment") -const userCache = require("./cache/user") -const { - getSessionsForUser, - invalidateSessions, -} = require("./security/sessions") -const events = require("./events") -const tenancy = require("./tenancy") const APP_PREFIX = DocumentType.APP + SEPARATOR const PROD_APP_PREFIX = "/app/" -function confirmAppId(possibleAppId) { +const BUILDER_PREVIEW_PATH = "/app/preview" +const BUILDER_REFERER_PREFIX = "/builder/app/" + +function confirmAppId(possibleAppId: string | undefined) { return possibleAppId && possibleAppId.startsWith(APP_PREFIX) ? possibleAppId : undefined } -async function resolveAppUrl(ctx) { +export async function resolveAppUrl(ctx: Ctx) { const appUrl = ctx.path.split("/")[2] let possibleAppUrl = `/${appUrl.toLowerCase()}` - let tenantId = tenancy.getTenantId() - if (!env.SELF_HOSTED && ctx.subdomains.length) { - // always use the tenant id from the url in cloud - tenantId = ctx.subdomains[0] + let tenantId: string | null = tenancy.getTenantId() + if (env.MULTI_TENANCY) { + // always use the tenant id from the subdomain in multi tenancy + // this ensures the logged-in user tenant id doesn't overwrite + // e.g. in the case of viewing a public app while already logged-in to another tenant + tenantId = tenancy.getTenantIDFromCtx(ctx, { + includeStrategies: [TenantResolutionStrategy.SUBDOMAIN], + }) } // search prod apps for a url that matches - const apps = await tenancy.doInTenant(tenantId, () => + const apps: App[] = await tenancy.doInTenant(tenantId, () => getAllApps({ dev: false }) ) const app = apps.filter( @@ -42,7 +59,7 @@ async function resolveAppUrl(ctx) { return app && app.appId ? app.appId : undefined } -exports.isServingApp = ctx => { +export function isServingApp(ctx: Ctx) { // dev app if (ctx.path.startsWith(`/${APP_PREFIX}`)) { return true @@ -59,12 +76,12 @@ exports.isServingApp = ctx => { * @param {object} ctx The main request body to look through. * @returns {string|undefined} If an appId was found it will be returned. */ -exports.getAppIdFromCtx = async ctx => { +export async function getAppIdFromCtx(ctx: Ctx) { // look in headers - const options = [ctx.headers[Headers.APP_ID]] + const options = [ctx.request.headers[Header.APP_ID]] let appId for (let option of options) { - appId = confirmAppId(option) + appId = confirmAppId(option as string) if (appId) { break } @@ -75,27 +92,46 @@ exports.getAppIdFromCtx = async ctx => { appId = confirmAppId(ctx.request.body.appId) } - // look in the url - dev app - let appPath = - ctx.request.headers.referrer || - ctx.path.split("/").filter(subPath => subPath.startsWith(APP_PREFIX)) - if (!appId && appPath.length) { - appId = confirmAppId(appPath[0]) + // look in the path + const pathId = parseAppIdFromUrl(ctx.path) + if (!appId && pathId) { + appId = confirmAppId(pathId) } - // look in the url - prod app - if (!appId && ctx.path.startsWith(PROD_APP_PREFIX)) { + // lookup using custom url - prod apps only + // filter out the builder preview path which collides with the prod app path + // to ensure we don't load all apps excessively + const isBuilderPreview = ctx.path.startsWith(BUILDER_PREVIEW_PATH) + const isViewingProdApp = + ctx.path.startsWith(PROD_APP_PREFIX) && !isBuilderPreview + if (!appId && isViewingProdApp) { appId = confirmAppId(await resolveAppUrl(ctx)) } + // look in the referer - builder only + // make sure this is performed after prod app url resolution, in case the + // referer header is present from a builder redirect + const referer = ctx.request.headers.referer + if (!appId && referer?.includes(BUILDER_REFERER_PREFIX)) { + const refererId = parseAppIdFromUrl(ctx.request.headers.referer) + appId = confirmAppId(refererId) + } + return appId } +function parseAppIdFromUrl(url?: string) { + if (!url) { + return + } + return url.split("/").find(subPath => subPath.startsWith(APP_PREFIX)) +} + /** * opens the contents of the specified encrypted JWT. * @return {object} the contents of the token. */ -exports.openJwt = token => { +export function openJwt(token: string) { if (!token) { return token } @@ -107,14 +143,14 @@ exports.openJwt = token => { * @param {object} ctx The request which is to be manipulated. * @param {string} name The name of the cookie to get. */ -exports.getCookie = (ctx, name) => { +export function getCookie(ctx: Ctx, name: string) { const cookie = ctx.cookies.get(name) if (!cookie) { return cookie } - return exports.openJwt(cookie) + return openJwt(cookie) } /** @@ -124,12 +160,17 @@ exports.getCookie = (ctx, name) => { * @param {string|object} value The value of cookie which will be set. * @param {object} opts options like whether to sign. */ -exports.setCookie = (ctx, value, name = "builder", opts = { sign: true }) => { +export function setCookie( + ctx: Ctx, + value: any, + name = "builder", + opts = { sign: true } +) { if (value && opts && opts.sign) { value = jwt.sign(value, options.secretOrKey) } - const config = { + const config: SetOption = { expires: MAX_VALID_DATE, path: "/", httpOnly: false, @@ -146,8 +187,8 @@ exports.setCookie = (ctx, value, name = "builder", opts = { sign: true }) => { /** * Utility function, simply calls setCookie with an empty string for value */ -exports.clearCookie = (ctx, name) => { - exports.setCookie(ctx, null, name) +export function clearCookie(ctx: Ctx, name: string) { + setCookie(ctx, null, name) } /** @@ -156,11 +197,11 @@ exports.clearCookie = (ctx, name) => { * @param {object} ctx The koa context object to be tested. * @return {boolean} returns true if the call is from the client lib (a built app rather than the builder). */ -exports.isClient = ctx => { - return ctx.headers[Headers.TYPE] === "client" +export function isClient(ctx: Ctx) { + return ctx.headers[Header.TYPE] === "client" } -const getBuilders = async () => { +async function getBuilders() { const builders = await queryGlobalView(ViewName.USER_BY_BUILDERS, { include_docs: false, }) @@ -176,7 +217,7 @@ const getBuilders = async () => { } } -exports.getBuildersCount = async () => { +export async function getBuildersCount() { const builders = await getBuilders() return builders.length } @@ -184,10 +225,14 @@ exports.getBuildersCount = async () => { /** * Logs a user out from budibase. Re-used across account portal and builder. */ -exports.platformLogout = async ({ ctx, userId, keepActiveSession }) => { +export async function platformLogout(opts: PlatformLogoutOpts) { + const ctx = opts.ctx + const userId = opts.userId + const keepActiveSession = opts.keepActiveSession + if (!ctx) throw new Error("Koa context must be supplied to logout.") - const currentSession = exports.getCookie(ctx, Cookies.Auth) + const currentSession = getCookie(ctx, Cookie.Auth) let sessions = await getSessionsForUser(userId) if (keepActiveSession) { @@ -196,8 +241,8 @@ exports.platformLogout = async ({ ctx, userId, keepActiveSession }) => { ) } else { // clear cookies - exports.clearCookie(ctx, Cookies.Auth) - exports.clearCookie(ctx, Cookies.CurrentApp) + clearCookie(ctx, Cookie.Auth) + clearCookie(ctx, Cookie.CurrentApp) } const sessionIds = sessions.map(({ sessionId }) => sessionId) @@ -206,6 +251,6 @@ exports.platformLogout = async ({ ctx, userId, keepActiveSession }) => { await userCache.invalidateUser(userId) } -exports.timeout = timeMs => { +export function timeout(timeMs: number) { return new Promise(resolve => setTimeout(resolve, timeMs)) } diff --git a/packages/backend-core/tenancy.js b/packages/backend-core/tenancy.js deleted file mode 100644 index 9ca808b74e..0000000000 --- a/packages/backend-core/tenancy.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require("./src/tenancy") diff --git a/packages/worker/scripts/jestSetup.js b/packages/backend-core/tests/jestSetup.ts similarity index 50% rename from packages/worker/scripts/jestSetup.js rename to packages/backend-core/tests/jestSetup.ts index 8ee2d33d70..b7ab5b49d9 100644 --- a/packages/worker/scripts/jestSetup.js +++ b/packages/backend-core/tests/jestSetup.ts @@ -1,19 +1,25 @@ -const env = require("../src/environment") +import env from "../src/environment" +import { mocks } from "./utilities" + +// must explicitly enable fetch mock +mocks.fetch.enable() + +// 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") -env._set("MULTI_TENANCY", true) +env._set("MINIO_URL", "http://localhost") +env._set("MINIO_ACCESS_KEY", "test") +env._set("MINIO_SECRET_KEY", "test") -const { mocks } = require("@budibase/backend-core/tests") - -// mock all dates to 2020-01-01T00:00:00.000Z -// use tk.reset() to use real dates in individual tests -const tk = require("timekeeper") -tk.freeze(mocks.date.MOCK_DATE) - -global.console.log = jest.fn() // console.log are ignored in tests +if (!process.env.DEBUG) { + global.console.log = jest.fn() // console.log are ignored in tests +} if (!process.env.CI) { // set a longer timeout in dev for debugging diff --git a/packages/backend-core/tests/utilities/TestConfiguration.js b/packages/backend-core/tests/utilities/TestConfiguration.js deleted file mode 100644 index 207b1d937f..0000000000 --- a/packages/backend-core/tests/utilities/TestConfiguration.js +++ /dev/null @@ -1 +0,0 @@ -require("./db") diff --git a/packages/backend-core/tests/utilities/db.js b/packages/backend-core/tests/utilities/db.js deleted file mode 100644 index 9d94bc2c7c..0000000000 --- a/packages/backend-core/tests/utilities/db.js +++ /dev/null @@ -1,6 +0,0 @@ -const core = require("../../src/index") -const dbConfig = { - inMemory: true, - allDbs: true, -} -core.init({ db: dbConfig }) diff --git a/packages/backend-core/tests/utilities/db.ts b/packages/backend-core/tests/utilities/db.ts new file mode 100644 index 0000000000..84b77bb201 --- /dev/null +++ b/packages/backend-core/tests/utilities/db.ts @@ -0,0 +1,9 @@ +import * as db from "../../src/db" + +const dbConfig = { + inMemory: true, +} + +export const init = () => { + db.init(dbConfig) +} diff --git a/packages/backend-core/tests/utilities/index.ts b/packages/backend-core/tests/utilities/index.ts index 1e73be4c17..ee96a94152 100644 --- a/packages/backend-core/tests/utilities/index.ts +++ b/packages/backend-core/tests/utilities/index.ts @@ -1,2 +1,7 @@ export * as mocks from "./mocks" export * as structures from "./structures" +export { generator } from "./structures" +export * as testEnv from "./testEnv" + +import * as dbConfig from "./db" +dbConfig.init() diff --git a/packages/backend-core/tests/utilities/mocks/accounts.ts b/packages/backend-core/tests/utilities/mocks/accounts.ts index 79436443db..e40d32b276 100644 --- a/packages/backend-core/tests/utilities/mocks/accounts.ts +++ b/packages/backend-core/tests/utilities/mocks/accounts.ts @@ -1,7 +1,13 @@ -export const getAccount = jest.fn() -export const getAccountByTenantId = jest.fn() +const mockGetAccount = jest.fn() +const mockGetAccountByTenantId = jest.fn() +const mockGetStatus = jest.fn() jest.mock("../../../src/cloud/accounts", () => ({ - getAccount, - getAccountByTenantId, + getAccount: mockGetAccount, + getAccountByTenantId: mockGetAccountByTenantId, + getStatus: mockGetStatus, })) + +export const getAccount = mockGetAccount +export const getAccountByTenantId = mockGetAccountByTenantId +export const getStatus = mockGetStatus diff --git a/packages/backend-core/tests/utilities/mocks/events.ts b/packages/backend-core/tests/utilities/mocks/events.ts index 415d59019d..ab0aaa93a6 100644 --- a/packages/backend-core/tests/utilities/mocks/events.ts +++ b/packages/backend-core/tests/utilities/mocks/events.ts @@ -1,9 +1,8 @@ -const processors = require("../../../src/events/processors") +import * as processors from "../../../src/events/processors" +import * as events from "../../../src/events" jest.spyOn(processors.analyticsProcessor, "processEvent") -const events = require("../../../src/events") - jest.spyOn(events.identification, "identifyTenantGroup") jest.spyOn(events.identification, "identifyUser") @@ -117,3 +116,7 @@ jest.spyOn(events.view, "filterDeleted") jest.spyOn(events.view, "calculationCreated") jest.spyOn(events.view, "calculationUpdated") jest.spyOn(events.view, "calculationDeleted") + +jest.spyOn(events.plugin, "init") +jest.spyOn(events.plugin, "imported") +jest.spyOn(events.plugin, "deleted") diff --git a/packages/backend-core/tests/utilities/mocks/fetch.ts b/packages/backend-core/tests/utilities/mocks/fetch.ts new file mode 100644 index 0000000000..eeb0ccda45 --- /dev/null +++ b/packages/backend-core/tests/utilities/mocks/fetch.ts @@ -0,0 +1,10 @@ +const mockFetch = jest.fn() + +const enable = () => { + jest.mock("node-fetch", () => mockFetch) +} + +export default { + ...mockFetch, + enable, +} diff --git a/packages/backend-core/tests/utilities/mocks/index.ts b/packages/backend-core/tests/utilities/mocks/index.ts index 7031b225ec..401fd7d7a7 100644 --- a/packages/backend-core/tests/utilities/mocks/index.ts +++ b/packages/backend-core/tests/utilities/mocks/index.ts @@ -1,4 +1,6 @@ -import "./posthog" -import "./events" export * as accounts from "./accounts" export * as date from "./date" +export * as licenses from "./licenses" +export { default as fetch } from "./fetch" +import "./posthog" +import "./events" diff --git a/packages/backend-core/tests/utilities/mocks/licenses.ts b/packages/backend-core/tests/utilities/mocks/licenses.ts new file mode 100644 index 0000000000..e374612f5f --- /dev/null +++ b/packages/backend-core/tests/utilities/mocks/licenses.ts @@ -0,0 +1,87 @@ +import { Feature, License, Quotas } from "@budibase/types" +import _ from "lodash" + +let CLOUD_FREE_LICENSE: License +let UNLIMITED_LICENSE: License +let getCachedLicense: any + +// init for the packages other than pro +export function init(proPkg: any) { + initInternal({ + CLOUD_FREE_LICENSE: proPkg.constants.licenses.CLOUD_FREE_LICENSE, + UNLIMITED_LICENSE: proPkg.constants.licenses.UNLIMITED_LICENSE, + getCachedLicense: proPkg.licensing.cache.getCachedLicense, + }) +} + +// init for the pro package +export function initInternal(opts: { + CLOUD_FREE_LICENSE: License + UNLIMITED_LICENSE: License + getCachedLicense: any +}) { + CLOUD_FREE_LICENSE = opts.CLOUD_FREE_LICENSE + UNLIMITED_LICENSE = opts.UNLIMITED_LICENSE + getCachedLicense = opts.getCachedLicense +} + +export interface UseLicenseOpts { + features?: Feature[] + quotas?: Quotas +} + +// LICENSES + +export const useLicense = (license: License, opts?: UseLicenseOpts) => { + if (opts) { + if (opts.features) { + license.features.push(...opts.features) + } + if (opts.quotas) { + license.quotas = opts.quotas + } + } + + getCachedLicense.mockReturnValue(license) + + return license +} + +export const useUnlimited = (opts?: UseLicenseOpts) => { + return useLicense(UNLIMITED_LICENSE, opts) +} + +export const useCloudFree = () => { + return useLicense(CLOUD_FREE_LICENSE) +} + +// FEATURES + +const useFeature = (feature: Feature) => { + const license = _.cloneDeep(UNLIMITED_LICENSE) + const opts: UseLicenseOpts = { + features: [feature], + } + + return useLicense(license, opts) +} + +export const useBackups = () => { + return useFeature(Feature.APP_BACKUPS) +} + +export const useGroups = () => { + return useFeature(Feature.USER_GROUPS) +} + +export const useEnvironmentVariables = () => { + return useFeature(Feature.ENVIRONMENT_VARIABLES) +} + +// QUOTAS + +export const setAutomationLogsQuota = (value: number) => { + const license = _.cloneDeep(UNLIMITED_LICENSE) + license.quotas.constant.automationLogRetentionDays.value = value + return useLicense(license) +} diff --git a/packages/backend-core/tests/utilities/structures/accounts.ts b/packages/backend-core/tests/utilities/structures/accounts.ts new file mode 100644 index 0000000000..f1718aecc0 --- /dev/null +++ b/packages/backend-core/tests/utilities/structures/accounts.ts @@ -0,0 +1,29 @@ +import { generator, uuid } from "." +import * as db from "../../../src/db/utils" +import { Account, AuthType, CloudAccount, Hosting } from "@budibase/types" + +export const account = (): Account => { + return { + accountId: uuid(), + tenantId: generator.word(), + email: generator.email(), + tenantName: generator.word(), + hosting: Hosting.SELF, + createdAt: Date.now(), + verified: true, + verificationSent: true, + tier: "FREE", // DEPRECATED + authType: AuthType.PASSWORD, + name: generator.name(), + size: "10+", + profession: "Software Engineer", + } +} + +export const cloudAccount = (): CloudAccount => { + return { + ...account(), + hosting: Hosting.CLOUD, + budibaseUserId: db.generateGlobalUserID(), + } +} diff --git a/packages/backend-core/tests/utilities/structures/apps.ts b/packages/backend-core/tests/utilities/structures/apps.ts new file mode 100644 index 0000000000..f3743d99b2 --- /dev/null +++ b/packages/backend-core/tests/utilities/structures/apps.ts @@ -0,0 +1,21 @@ +import { generator } from "." +import { App } from "@budibase/types" +import { DEFAULT_TENANT_ID, DocumentType } from "../../../src/constants" + +export function app(id: string): App { + return { + _id: DocumentType.APP_METADATA, + appId: id, + type: "", + version: "0.0.1", + componentLibraries: [], + name: generator.name(), + url: `/custom-url`, + instance: { + _id: id, + }, + tenantId: DEFAULT_TENANT_ID, + status: "", + template: undefined, + } +} diff --git a/packages/backend-core/tests/utilities/structures/common.ts b/packages/backend-core/tests/utilities/structures/common.ts new file mode 100644 index 0000000000..05b879f36b --- /dev/null +++ b/packages/backend-core/tests/utilities/structures/common.ts @@ -0,0 +1,7 @@ +import { v4 as uuid } from "uuid" + +export { v4 as uuid } from "uuid" + +export const email = () => { + return `${uuid()}@test.com` +} diff --git a/packages/backend-core/tests/utilities/structures/index.ts b/packages/backend-core/tests/utilities/structures/index.ts index 12b6ab7ad6..0d0f0c507f 100644 --- a/packages/backend-core/tests/utilities/structures/index.ts +++ b/packages/backend-core/tests/utilities/structures/index.ts @@ -1 +1,10 @@ +export * from "./common" + +import Chance from "chance" +export const generator = new Chance() + +export * as accounts from "./accounts" +export * as apps from "./apps" export * as koa from "./koa" +export * as licenses from "./licenses" +export * as plugins from "./plugins" diff --git a/packages/backend-core/tests/utilities/structures/koa.ts b/packages/backend-core/tests/utilities/structures/koa.ts index 6f0f7866e6..102fe029de 100644 --- a/packages/backend-core/tests/utilities/structures/koa.ts +++ b/packages/backend-core/tests/utilities/structures/koa.ts @@ -1,5 +1,16 @@ -import { createMockContext } from "@shopify/jest-koa-mocks" +import { createMockContext, createMockCookies } from "@shopify/jest-koa-mocks" +import { BBContext } from "@budibase/types" -export const newContext = () => { - return createMockContext() +export const newContext = (): BBContext => { + const ctx = createMockContext() + return { + ...ctx, + path: "/", + cookies: createMockCookies(), + request: { + ...ctx.request, + headers: {}, + body: {}, + }, + } } diff --git a/packages/backend-core/tests/utilities/structures/licenses.ts b/packages/backend-core/tests/utilities/structures/licenses.ts new file mode 100644 index 0000000000..a541e91860 --- /dev/null +++ b/packages/backend-core/tests/utilities/structures/licenses.ts @@ -0,0 +1,18 @@ +import { AccountPlan, License, PlanType, Quotas } from "@budibase/types" + +const newPlan = (type: PlanType = PlanType.FREE): AccountPlan => { + return { + type, + } +} + +export const newLicense = (opts: { + quotas: Quotas + planType?: PlanType +}): License => { + return { + features: [], + quotas: opts.quotas, + plan: newPlan(opts.planType), + } +} diff --git a/packages/backend-core/tests/utilities/structures/plugins.ts b/packages/backend-core/tests/utilities/structures/plugins.ts new file mode 100644 index 0000000000..e2d92858d3 --- /dev/null +++ b/packages/backend-core/tests/utilities/structures/plugins.ts @@ -0,0 +1,19 @@ +import { generator } from "." +import { Plugin, PluginSource, PluginType } from "@budibase/types" + +export function plugin(): Plugin { + return { + description: generator.word(), + name: generator.word(), + version: "1.0.0", + source: PluginSource.FILE, + package: { + name: generator.word, + }, + hash: generator.hash(), + schema: { + type: PluginType.DATASOURCE, + }, + iconFileName: "icon.svg", + } +} diff --git a/packages/backend-core/tests/utilities/testEnv.ts b/packages/backend-core/tests/utilities/testEnv.ts new file mode 100644 index 0000000000..b4f06b5153 --- /dev/null +++ b/packages/backend-core/tests/utilities/testEnv.ts @@ -0,0 +1,87 @@ +import env from "../../src/environment" +import * as tenancy from "../../src/tenancy" +import { newid } from "../../src/utils" + +// TENANCY + +export async function withTenant(task: (tenantId: string) => any) { + const tenantId = newid() + return tenancy.doInTenant(tenantId, async () => { + await task(tenantId) + }) +} + +export function singleTenant() { + env._set("MULTI_TENANCY", 0) +} + +export function multiTenant() { + env._set("MULTI_TENANCY", 1) +} + +// NODE + +export function nodeDev() { + env._set("NODE_ENV", "dev") +} + +export function nodeJest() { + env._set("NODE_ENV", "jest") +} + +// FILES + +export function withS3() { + env._set("NODE_ENV", "production") + env._set("MINIO_ENABLED", 0) + env._set("MINIO_URL", "http://s3.example.com") + env._set("CLOUDFRONT_CDN", undefined) +} + +const CLOUDFRONT_TEST_KEY = + "-----BEGIN RSA PRIVATE KEY-----\n" + + "MIIEpAIBAAKCAQEAqXRsir/0Qba1xEnybUs7d7QEAE02GRc+4H7HD5l5VnAxkV1m\n" + + "tNTXTmoYkaIhLdebV1EwQs3T9knxoyd4cVcrDkDfDLZErfYWJsuE3/QYNknnZs4/\n" + + "Ai0cg+v9ZX3gcizvpYg9GQI3INM0uRG8lJwGP7FQ/kknhA2yVFVCSxX6kkNtOUh5\n" + + "dKSG7m6IwswcSwD++Z/94vsFkoZIGY0e1CD/drFJ6+1TFY2YgbDKT5wDFLJ9vHFx\n" + + "/5o4POwn3gz/ru2Db9jbRdfEAqRdy46nRKQgBGUmupAgSK1+BJEzafexp8RmCGb0\n" + + "WUffxOtj8/jNCeCF0JBgVHAe3crOQ8ySrtoaHQIDAQABAoIBAA+ipW07/u6dTDI7\n" + + "XHoHKgqGeqQIe8he47dVG0ruL0rxeTFfe92NkfwzP+cYHZWcQkIRRLG1Six8cCZM\n" + + "uwlCML/U7n++xaGDhlG4D5+WZzGDKi3LM/cgcHQfrzbRIYeHa+lLI9AN60ZFFqVI\n" + + "5KyVpOH1m3KLD3FYzi6H22EQOxmJpqWlt2uArny5LxlPJKmmGSFjvneb4N2ZAKGQ\n" + + "QfClJGz9tRjceWUUdJrpqmTmBQIosKmLPq8PEviUNAVG+6m4r8jiRbf8OKkAm+3L\n" + + "LVIsN8HfYB9jEuERYPnbuXdX0kDEkg0xEyTH5YbNZvfm5ptCU9Xn+Jz1trF+wCHD\n" + + "2RlxdQUCgYEA3U0nCf6NTmmeMCsAX6gvaPuM0iUfUfS3b3G57I6u46lLGNLsfJw6\n" + + "MTpVc164lKYQK9czw/ijKzb8e3mcyzbPorVkajMjUCNWGrMK+vFbOGmqQkhUi30U\n" + + "IJuuTktMd+21D/SpLlev4MLria23vUIKEqNenYpV6wkGLt/mKtISaPMCgYEAxAYx\n" + + "j+xJLTK9eN+rpekwjYE78hD9VoBkBnr/NBiGV302AsJRuq2+L4zcBnAsH+SidFim\n" + + "cwqoj3jeVT8ZQFXlK3fGVaEJsCXd6GWk8ZIWUTn9JZwi2KcCvCU/YiHfx8c7y7Gl\n" + + "SiPXUPsvvkcw6RRh2u4J5tHLIqJe3W58ENoBNK8CgYEApxTBDMKrXTBQxn0w4wfQ\n" + + "A6soPuDYLMBeXj226eswD6KZmDxnYA1zwgcQzPIO2ewm+XKZGrR2PQJezbqbrrHL\n" + + "QkVBcwz49GA5eh8Dg0MGZCki6rhBXK8qqxPfHi2rpkBKG6nUsbBykXeY7XHC75kU\n" + + "kc3WeYsgIzvE908EMAA69hECgYEAinbpiYVZh1DBH+G26MIYZswz4OB5YyHcBevZ\n" + + "2x27v48VmMtUWe4iWopAXVfdA0ZILrD0Gm0b9gRl4IdqudQyxgqcEZ5oLoIBBwjN\n" + + "g0oy83tnwqpQvwLx3p7c79+HqCGmrlK0s/MvQ+e6qMi21t1r5e6hFed5euSA6B8E\n" + + "Cg9ELMcCgYB9bGwlNAE+iuzMIhKev1s7h3TzqKtGw37TtHXvxcTQs3uawJQksQ2s\n" + + "K0Zy1Ta7vybbwAA5m+LxoMT04WUdJO7Cr8/3rBMrbKKO3H7IgC3G+nXnOBdshzn5\n" + + "ifMbhZslFThC/osD5ZV7snXZgTWyPexaINJhHmdrAWpmW1h+UFoiMw==\n" + + "-----END RSA PRIVATE KEY-----\n" + +const CLOUDFRONT_TEST_KEY_64 = Buffer.from( + CLOUDFRONT_TEST_KEY, + "utf-8" +).toString("base64") + +export function withCloudfront() { + withS3() + env._set("CLOUDFRONT_CDN", "http://cf.example.com") + env._set("CLOUDFRONT_PUBLIC_KEY_ID", "keypair_123") + env._set("CLOUDFRONT_PRIVATE_KEY_64", CLOUDFRONT_TEST_KEY_64) +} + +export function withMinio() { + env._set("NODE_ENV", "production") + env._set("MINIO_ENABLED", 1) + env._set("MINIO_URL", "http://minio.example.com") + env._set("CLOUDFRONT_CDN", undefined) +} diff --git a/packages/backend-core/tsconfig.build.json b/packages/backend-core/tsconfig.build.json index 40ffe6b827..12f8255a7c 100644 --- a/packages/backend-core/tsconfig.build.json +++ b/packages/backend-core/tsconfig.build.json @@ -3,7 +3,6 @@ "target": "es6", "module": "commonjs", "lib": ["es2020"], - "allowJs": true, "strict": true, "noImplicitAny": true, "esModuleInterop": true, @@ -12,7 +11,8 @@ "sourceMap": true, "declaration": true, "types": [ "node", "jest" ], - "outDir": "dist" + "outDir": "dist", + "skipLibCheck": true }, "include": [ "**/*.js", @@ -23,6 +23,7 @@ "node_modules", "dist", "**/*.spec.ts", - "**/*.spec.js" + "**/*.spec.js", + "__mocks__" ] } \ No newline at end of file diff --git a/packages/backend-core/tsconfig.json b/packages/backend-core/tsconfig.json index ccefd149a0..e95fb9ab4d 100644 --- a/packages/backend-core/tsconfig.json +++ b/packages/backend-core/tsconfig.json @@ -8,6 +8,10 @@ } }, "references": [ - { "path": "../types" }, + { "path": "../types" } + ], + "exclude": [ + "node_modules", + "dist", ] } \ No newline at end of file diff --git a/packages/backend-core/utils.js b/packages/backend-core/utils.js deleted file mode 100644 index 2ef920e103..0000000000 --- a/packages/backend-core/utils.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - ...require("./src/utils"), - ...require("./src/hashing"), -} diff --git a/packages/backend-core/yarn.lock b/packages/backend-core/yarn.lock index 2e62aea734..03393a4ea8 100644 --- a/packages/backend-core/yarn.lock +++ b/packages/backend-core/yarn.lock @@ -10,150 +10,156 @@ "@jridgewell/gen-mapping" "^0.1.0" "@jridgewell/trace-mapping" "^0.3.9" -"@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== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" + integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== dependencies: - "@babel/highlight" "^7.16.7" + "@babel/highlight" "^7.18.6" -"@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/compat-data@^7.20.5": + version "7.20.10" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.10.tgz#9d92fa81b87542fff50e848ed585b4212c1d34ec" + integrity sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg== -"@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== +"@babel/core@^7.11.6", "@babel/core@^7.12.3": + version "7.20.12" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.12.tgz#7930db57443c6714ad216953d1356dac0eb8496d" + integrity sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg== 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" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.20.7" + "@babel/helper-compilation-targets" "^7.20.7" + "@babel/helper-module-transforms" "^7.20.11" + "@babel/helpers" "^7.20.7" + "@babel/parser" "^7.20.7" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.20.12" + "@babel/types" "^7.20.7" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" - json5 "^2.2.1" + json5 "^2.2.2" semver "^6.3.0" -"@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== +"@babel/generator@^7.20.7", "@babel/generator@^7.7.2": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.7.tgz#f8ef57c8242665c5929fe2e8d82ba75460187b4a" + integrity sha512-7wqMOJq8doJMZmP4ApXTzLxSr7+oO2jroJURrVEp6XShrQUObV8Tq/D0NCcoYg2uHqUrjzO0zwBjoYzelxK+sw== dependencies: - "@babel/types" "^7.18.2" - "@jridgewell/gen-mapping" "^0.3.0" + "@babel/types" "^7.20.7" + "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" -"@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== +"@babel/helper-compilation-targets@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz#a6cd33e93629f5eb473b021aac05df62c4cd09bb" + integrity sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ== dependencies: - "@babel/compat-data" "^7.17.10" - "@babel/helper-validator-option" "^7.16.7" - browserslist "^4.20.2" + "@babel/compat-data" "^7.20.5" + "@babel/helper-validator-option" "^7.18.6" + browserslist "^4.21.3" + lru-cache "^5.1.1" semver "^6.3.0" -"@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-environment-visitor@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" + integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== -"@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== +"@babel/helper-function-name@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c" + integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== dependencies: - "@babel/template" "^7.16.7" - "@babel/types" "^7.17.0" + "@babel/template" "^7.18.10" + "@babel/types" "^7.19.0" -"@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== +"@babel/helper-hoist-variables@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" + integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== dependencies: - "@babel/types" "^7.16.7" + "@babel/types" "^7.18.6" -"@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== +"@babel/helper-module-imports@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" + integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== dependencies: - "@babel/types" "^7.16.7" + "@babel/types" "^7.18.6" -"@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== +"@babel/helper-module-transforms@^7.20.11": + version "7.20.11" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz#df4c7af713c557938c50ea3ad0117a7944b2f1b0" + integrity sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg== dependencies: - "@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-environment-visitor" "^7.18.9" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-simple-access" "^7.20.2" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.19.1" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.20.10" + "@babel/types" "^7.20.7" -"@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-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.19.0", "@babel/helper-plugin-utils@^7.8.0": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629" + integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ== -"@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== +"@babel/helper-simple-access@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz#0ab452687fe0c2cfb1e2b9e0015de07fc2d62dd9" + integrity sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA== dependencies: - "@babel/types" "^7.18.2" + "@babel/types" "^7.20.2" -"@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== +"@babel/helper-split-export-declaration@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" + integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== dependencies: - "@babel/types" "^7.16.7" + "@babel/types" "^7.18.6" -"@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-string-parser@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" + integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== -"@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/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" + integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== -"@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== +"@babel/helper-validator-option@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" + integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== + +"@babel/helpers@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.7.tgz#04502ff0feecc9f20ecfaad120a18f011a8e6dce" + integrity sha512-PBPjs5BppzsGaxHQCDKnZ6Gd9s6xl8bBCluz3vEInLGRJmnZan4F6BYCeqtyXqkk4W5IlPmjK4JlOuZkpJ3xZA== dependencies: - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.18.2" - "@babel/types" "^7.18.2" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.20.7" + "@babel/types" "^7.20.7" -"@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== +"@babel/highlight@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" + integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== dependencies: - "@babel/helper-validator-identifier" "^7.16.7" + "@babel/helper-validator-identifier" "^7.18.6" chalk "^2.0.0" js-tokens "^4.0.0" -"@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/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.7.tgz#66fe23b3c8569220817d5feb8b9dcdc95bb4f71b" + integrity sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg== "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -240,50 +246,51 @@ "@babel/helper-plugin-utils" "^7.14.5" "@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== + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz#4e9a0cfc769c85689b77a2e642d24e9f697fc8c7" + integrity sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ== dependencies: - "@babel/helper-plugin-utils" "^7.17.12" + "@babel/helper-plugin-utils" "^7.19.0" "@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== + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.7.tgz#fcb41a5a70550e04a7b708037c7c32f7f356d8fd" + integrity sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ== dependencies: - regenerator-runtime "^0.13.4" + regenerator-runtime "^0.13.11" -"@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== +"@babel/template@^7.18.10", "@babel/template@^7.20.7", "@babel/template@^7.3.3": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8" + integrity sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw== dependencies: - "@babel/code-frame" "^7.16.7" - "@babel/parser" "^7.16.7" - "@babel/types" "^7.16.7" + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.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== +"@babel/traverse@^7.20.10", "@babel/traverse@^7.20.12", "@babel/traverse@^7.20.7", "@babel/traverse@^7.7.2": + version "7.20.12" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.12.tgz#7f0f787b3a67ca4475adef1f56cb94f6abd4a4b5" + integrity sha512-MsIbFN0u+raeja38qboyF8TIT7K0BFzz/Yd/77ta4MsUsmP2RAnidIlwq7d5HFQrH/OZJecGV6B71C4zAgpoSQ== 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" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.20.7" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" debug "^4.1.0" globals "^11.1.0" -"@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== +"@babel/types@^7.0.0", "@babel/types@^7.18.6", "@babel/types@^7.19.0", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.3.0", "@babel/types@^7.3.3": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.7.tgz#54ec75e252318423fc07fb644dc6a58a64c09b7f" + integrity sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg== dependencies: - "@babel/helper-validator-identifier" "^7.16.7" + "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" "@bcoe/v8-coverage@^0.2.3": @@ -291,6 +298,25 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@budibase/nano@10.1.1": + version "10.1.1" + resolved "https://registry.yarnpkg.com/@budibase/nano/-/nano-10.1.1.tgz#36ccda4d9bb64b5ee14dd2b27a295b40739b1038" + integrity sha512-kbMIzMkjVtl+xI0UPwVU0/pn8/ccxTyfzwBz6Z+ZiN2oUSb0fJCe0qwA6o8dxwSa8nZu4MbGAeMJl3CJndmWtA== + dependencies: + "@types/tough-cookie" "^4.0.2" + axios "^1.1.3" + http-cookie-agent "^4.0.2" + node-abort-controller "^3.0.1" + qs "^6.11.0" + tough-cookie "^4.1.2" + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + "@hapi/hoek@^9.0.0": version "9.3.0" resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" @@ -319,163 +345,193 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@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== +"@jest/console@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-28.1.3.tgz#2030606ec03a18c31803b8a36382762e447655df" + integrity sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw== dependencies: - "@jest/types" "^27.5.1" + "@jest/types" "^28.1.3" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^27.5.1" - jest-util "^27.5.1" + jest-message-util "^28.1.3" + jest-util "^28.1.3" slash "^3.0.0" -"@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== +"@jest/core@^28.1.1", "@jest/core@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-28.1.3.tgz#0ebf2bd39840f1233cd5f2d1e6fc8b71bd5a1ac7" + integrity sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA== dependencies: - "@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" + "@jest/console" "^28.1.3" + "@jest/reporters" "^28.1.3" + "@jest/test-result" "^28.1.3" + "@jest/transform" "^28.1.3" + "@jest/types" "^28.1.3" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" - emittery "^0.8.1" + ci-info "^3.2.0" exit "^0.1.2" 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" + jest-changed-files "^28.1.3" + jest-config "^28.1.3" + jest-haste-map "^28.1.3" + jest-message-util "^28.1.3" + jest-regex-util "^28.0.2" + jest-resolve "^28.1.3" + jest-resolve-dependencies "^28.1.3" + jest-runner "^28.1.3" + jest-runtime "^28.1.3" + jest-snapshot "^28.1.3" + jest-util "^28.1.3" + jest-validate "^28.1.3" + jest-watcher "^28.1.3" micromatch "^4.0.4" + pretty-format "^28.1.3" rimraf "^3.0.0" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^27.5.1": +"@jest/create-cache-key-function@^27.4.2": version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-27.5.1.tgz#d7425820511fe7158abbecc010140c3fd3be9c74" - integrity sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA== + resolved "https://registry.yarnpkg.com/@jest/create-cache-key-function/-/create-cache-key-function-27.5.1.tgz#7448fae15602ea95c828f5eceed35c202a820b31" + integrity sha512-dmH1yW+makpTSURTy8VzdUwFnfQh1G8R+DxO2Ho2FFmBbKFEVm+3jWdvFhE2VqB/LATCTokkP0dotjyQyw5/AQ== dependencies: - "@jest/fake-timers" "^27.5.1" "@jest/types" "^27.5.1" + +"@jest/environment@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-28.1.3.tgz#abed43a6b040a4c24fdcb69eab1f97589b2d663e" + integrity sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA== + dependencies: + "@jest/fake-timers" "^28.1.3" + "@jest/types" "^28.1.3" "@types/node" "*" - jest-mock "^27.5.1" + jest-mock "^28.1.3" -"@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== +"@jest/expect-utils@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-28.1.3.tgz#58561ce5db7cd253a7edddbc051fb39dda50f525" + integrity sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA== dependencies: - "@jest/types" "^27.5.1" - "@sinonjs/fake-timers" "^8.0.1" + jest-get-type "^28.0.2" + +"@jest/expect@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-28.1.3.tgz#9ac57e1d4491baca550f6bdbd232487177ad6a72" + integrity sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw== + dependencies: + expect "^28.1.3" + jest-snapshot "^28.1.3" + +"@jest/fake-timers@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-28.1.3.tgz#230255b3ad0a3d4978f1d06f70685baea91c640e" + integrity sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw== + dependencies: + "@jest/types" "^28.1.3" + "@sinonjs/fake-timers" "^9.1.2" "@types/node" "*" - jest-message-util "^27.5.1" - jest-mock "^27.5.1" - jest-util "^27.5.1" + jest-message-util "^28.1.3" + jest-mock "^28.1.3" + jest-util "^28.1.3" -"@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== +"@jest/globals@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-28.1.3.tgz#a601d78ddc5fdef542728309894895b4a42dc333" + integrity sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA== dependencies: - "@jest/environment" "^27.5.1" - "@jest/types" "^27.5.1" - expect "^27.5.1" + "@jest/environment" "^28.1.3" + "@jest/expect" "^28.1.3" + "@jest/types" "^28.1.3" -"@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== +"@jest/reporters@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-28.1.3.tgz#9adf6d265edafc5fc4a434cfb31e2df5a67a369a" + integrity sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/console" "^28.1.3" + "@jest/test-result" "^28.1.3" + "@jest/transform" "^28.1.3" + "@jest/types" "^28.1.3" + "@jridgewell/trace-mapping" "^0.3.13" "@types/node" "*" chalk "^4.0.0" collect-v8-coverage "^1.0.0" exit "^0.1.2" - glob "^7.1.2" + glob "^7.1.3" graceful-fs "^4.2.9" istanbul-lib-coverage "^3.0.0" istanbul-lib-instrument "^5.1.0" istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" 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" + jest-message-util "^28.1.3" + jest-util "^28.1.3" + jest-worker "^28.1.3" slash "^3.0.0" - source-map "^0.6.0" string-length "^4.0.1" + strip-ansi "^6.0.0" terminal-link "^2.0.0" - v8-to-istanbul "^8.1.0" + v8-to-istanbul "^9.0.1" -"@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== +"@jest/schemas@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-28.1.3.tgz#ad8b86a66f11f33619e3d7e1dcddd7f2d40ff905" + integrity sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg== dependencies: + "@sinclair/typebox" "^0.24.1" + +"@jest/source-map@^28.1.2": + version "28.1.2" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-28.1.2.tgz#7fe832b172b497d6663cdff6c13b0a920e139e24" + integrity sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww== + dependencies: + "@jridgewell/trace-mapping" "^0.3.13" callsites "^3.0.0" graceful-fs "^4.2.9" - source-map "^0.6.0" -"@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== +"@jest/test-result@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-28.1.3.tgz#5eae945fd9f4b8fcfce74d239e6f725b6bf076c5" + integrity sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg== dependencies: - "@jest/console" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/console" "^28.1.3" + "@jest/types" "^28.1.3" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@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== +"@jest/test-sequencer@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz#9d0c283d906ac599c74bde464bc0d7e6a82886c3" + integrity sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw== dependencies: - "@jest/test-result" "^27.5.1" + "@jest/test-result" "^28.1.3" graceful-fs "^4.2.9" - jest-haste-map "^27.5.1" - jest-runtime "^27.5.1" + jest-haste-map "^28.1.3" + slash "^3.0.0" -"@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== +"@jest/transform@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-28.1.3.tgz#59d8098e50ab07950e0f2fc0fc7ec462371281b0" + integrity sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA== dependencies: - "@babel/core" "^7.1.0" - "@jest/types" "^27.5.1" + "@babel/core" "^7.11.6" + "@jest/types" "^28.1.3" + "@jridgewell/trace-mapping" "^0.3.13" 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.9" - jest-haste-map "^27.5.1" - jest-regex-util "^27.5.1" - jest-util "^27.5.1" + jest-haste-map "^28.1.3" + jest-regex-util "^28.0.2" + jest-util "^28.1.3" micromatch "^4.0.4" pirates "^4.0.4" slash "^3.0.0" - source-map "^0.6.1" - write-file-atomic "^3.0.0" + write-file-atomic "^4.0.1" "@jest/types@^27.5.1": version "27.5.1" @@ -488,6 +544,18 @@ "@types/yargs" "^16.0.0" chalk "^4.0.0" +"@jest/types@^28.1.1", "@jest/types@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-28.1.3.tgz#b05de80996ff12512bc5ceb1d208285a7d11748b" + integrity sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ== + dependencies: + "@jest/schemas" "^28.1.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + 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" @@ -496,42 +564,50 @@ "@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== +"@jridgewell/gen-mapping@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== dependencies: - "@jridgewell/set-array" "^1.0.0" + "@jridgewell/set-array" "^1.0.1" "@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/resolve-uri@3.1.0", "@jridgewell/resolve-uri@^3.0.3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== -"@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/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== -"@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/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== -"@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== +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== dependencies: "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.13", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.17" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" + integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== + dependencies: + "@jridgewell/resolve-uri" "3.1.0" + "@jridgewell/sourcemap-codec" "1.4.14" + "@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== + version "1.0.10" + resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz#8e6735ccebbb1581e5a7e652244cadc8a844d03c" + integrity sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA== dependencies: detect-libc "^2.0.0" https-proxy-agent "^5.0.0" @@ -543,6 +619,36 @@ semver "^7.3.5" tar "^6.1.11" +"@msgpackr-extract/msgpackr-extract-darwin-arm64@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-2.2.0.tgz#901c5937e1441572ea23e631fe6deca68482fe76" + integrity sha512-Z9LFPzfoJi4mflGWV+rv7o7ZbMU5oAU9VmzCgL240KnqDW65Y2HFCT3MW06/ITJSnbVLacmcEJA8phywK7JinQ== + +"@msgpackr-extract/msgpackr-extract-darwin-x64@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-2.2.0.tgz#fb877fe6bae3c4d3cea29786737840e2ae689066" + integrity sha512-vq0tT8sjZsy4JdSqmadWVw6f66UXqUCabLmUVHZwUFzMgtgoIIQjT4VVRHKvlof3P/dMCkbMJ5hB1oJ9OWHaaw== + +"@msgpackr-extract/msgpackr-extract-linux-arm64@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-2.2.0.tgz#986179c38b10ac41fbdaf7d036c825cbc72855d9" + integrity sha512-hlxxLdRmPyq16QCutUtP8Tm6RDWcyaLsRssaHROatgnkOxdleMTgetf9JsdncL8vLh7FVy/RN9i3XR5dnb9cRA== + +"@msgpackr-extract/msgpackr-extract-linux-arm@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-2.2.0.tgz#15f2c6fe9e0adc06c21af7e95f484ff4880d79ce" + integrity sha512-SaJ3Qq4lX9Syd2xEo9u3qPxi/OB+5JO/ngJKK97XDpa1C587H9EWYO6KD8995DAjSinWvdHKRrCOXVUC5fvGOg== + +"@msgpackr-extract/msgpackr-extract-linux-x64@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-2.2.0.tgz#30cae5c9a202f3e1fa1deb3191b18ffcb2f239a2" + integrity sha512-94y5PJrSOqUNcFKmOl7z319FelCLAE0rz/jPCWS+UtdMZvpa4jrQd+cJPQCLp2Fes1yAW/YUQj/Di6YVT3c3Iw== + +"@msgpackr-extract/msgpackr-extract-win32-x64@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-2.2.0.tgz#016d855b6bc459fd908095811f6826e45dd4ba64" + integrity sha512-XrC0JzsqQSvOyM3t04FMLO6z5gCuhPE6k4FXuLK5xf52ZbdvcFe1yBmo7meCew9B8G2f0T9iu9t3kfTYRYROgA== + "@shopify/jest-koa-mocks@5.0.1": version "5.0.1" resolved "https://registry.yarnpkg.com/@shopify/jest-koa-mocks/-/jest-koa-mocks-5.0.1.tgz#fba490b6b7985fbb571eb9974897d396a3642e94" @@ -559,34 +665,113 @@ "@hapi/hoek" "^9.0.0" "@sideway/formula@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.0.tgz#fe158aee32e6bd5de85044be615bc08478a0a13c" - integrity sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg== + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" + integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== "@sideway/pinpoint@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== +"@sinclair/typebox@^0.24.1": + version "0.24.51" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.51.tgz#645f33fe4e02defe26f2f5c0410e1c094eac7f5f" + integrity sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA== + "@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" - integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== + version "1.8.6" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.6.tgz#80c516a4dc264c2a69115e7578d62581ff455ed9" + integrity sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ== dependencies: type-detect "4.0.8" -"@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== +"@sinonjs/fake-timers@^9.1.2": + version "9.1.2" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz#4eaab737fab77332ab132d396a3c0d364bd0ea8c" + integrity sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw== dependencies: "@sinonjs/commons" "^1.7.0" +"@swc/core-darwin-arm64@1.3.25": + version "1.3.25" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.25.tgz#01ce7b8a88b545a4fc5283ed6f96b22c5733d6c4" + integrity sha512-8PWAVcjTJyj2VrqPBFOIi2w2P0Z8kOCbzHW3+pe+bSXxfGMG0MKPl5U2IXhsEL0ovm4xSFlqW0yygpoP3MmRPw== + +"@swc/core-darwin-x64@1.3.25": + version "1.3.25" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.3.25.tgz#9fad102c507011f42c5a5d1f84919b81ab96d7f8" + integrity sha512-5DHGiMYFEj5aa208tCjo7Sn5tiG4xPz+4gUiWVlglxqXFptkNim5xu/1G6VYm5Zk7dI5jJkjTU76GQG7IRvPug== + +"@swc/core-linux-arm-gnueabihf@1.3.25": + version "1.3.25" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.25.tgz#ecf3a34899fdbdc742523524caab29c0db97a6ad" + integrity sha512-YNfLxv9PhZk+jrJbpR1mMrYBUkufo0hiFv3S1OrX3l8edsIP4wPND5w9ZH0Oi898f6Jg9DBrY2zXJMQ+gWkbvA== + +"@swc/core-linux-arm64-gnu@1.3.25": + version "1.3.25" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.25.tgz#50524c9db2dbf874570e45f0a66e0283f02bc2d9" + integrity sha512-kS+spM5/xQ6QvWF1ms3byfjnhUlpjTfFwgCyHnIKgjvsYkDa+vkAIhKq6HuEdaTPaCRCjts0Zarhub1nClUU0g== + +"@swc/core-linux-arm64-musl@1.3.25": + version "1.3.25" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.25.tgz#f04a3d3784cff14f96ad9901861485ec0fa14ebf" + integrity sha512-vM3D7LWmjotUAJ2D4F+L+dspFeWrcPNVh0o8TCoTOYCt8DPD5YsUKTpIgOsZ+gReeWUAnNTh0Btx5pGGVfajGA== + +"@swc/core-linux-x64-gnu@1.3.25": + version "1.3.25" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.25.tgz#761fb020b8a0130e4dccc9c8dce355fa06df63f4" + integrity sha512-xUCLLMDlYa/zB8BftVa4SrxuVpcDxkltCfmBg5r2pZPVskhC5ZJsQZ/AvWNChoAB11shRhjTaWDlmxJEsa7TIg== + +"@swc/core-linux-x64-musl@1.3.25": + version "1.3.25" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.25.tgz#f944ee48c972ebdcb3e6d6fd62d67eb98dbb1268" + integrity sha512-QzHU3BIaUVRSFNsUn3Qxx1vgtF/f5NqsFMAAPSq9Y8Yq5nrlc2t7cNuOROxHLbUqE+NPUp6+RglleJMoeWz5mA== + +"@swc/core-win32-arm64-msvc@1.3.25": + version "1.3.25" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.25.tgz#af63ae850ef6e7322e8a5a0959529e96096239d2" + integrity sha512-77VSVtneVOAUL4zkRyQZ6pWVpTsVVdqwly/DKnRnloglGKxYuk5DG5MUBsL72Nnfv4OCHjZ27eI3NUrpLsUb2Q== + +"@swc/core-win32-ia32-msvc@1.3.25": + version "1.3.25" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.25.tgz#96a869aa4b4c41c44c9c9893ac4aad68d1233022" + integrity sha512-kz0v3K3H6OPEZR3ry72Ad/6C5GrZBRRUk69K58LORQ8tZXQD3UGl85pUbQqyHl8fR5NU76Muxgovj9CI9iTHGA== + +"@swc/core-win32-x64-msvc@1.3.25": + version "1.3.25" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.25.tgz#9035c11626653322a404f3f44af11a02d989094c" + integrity sha512-nmQOAzIpNRRnupWzkenJmW4i+h1M76cVNUqEU2MjmtesEkRZEGqv//jefXiyCP2zcbeLNLKiB2ptVJhpd1BvRA== + +"@swc/core@^1.3.25": + version "1.3.25" + resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.3.25.tgz#53786ea51fac319684d6822de1738eb55b73a4b7" + integrity sha512-wqzvM/wu6OsTVYPMStOpm7kIQcPX3GoZ0sC85qzDdsCxmJ1rmItLAD91sXPUmmdk0XqPYjLgT9MRDEIP5woz4g== + optionalDependencies: + "@swc/core-darwin-arm64" "1.3.25" + "@swc/core-darwin-x64" "1.3.25" + "@swc/core-linux-arm-gnueabihf" "1.3.25" + "@swc/core-linux-arm64-gnu" "1.3.25" + "@swc/core-linux-arm64-musl" "1.3.25" + "@swc/core-linux-x64-gnu" "1.3.25" + "@swc/core-linux-x64-musl" "1.3.25" + "@swc/core-win32-arm64-msvc" "1.3.25" + "@swc/core-win32-ia32-msvc" "1.3.25" + "@swc/core-win32-x64-msvc" "1.3.25" + +"@swc/jest@^0.2.24": + version "0.2.24" + resolved "https://registry.yarnpkg.com/@swc/jest/-/jest-0.2.24.tgz#35d9377ede049613cd5fdd6c24af2b8dcf622875" + integrity sha512-fwgxQbM1wXzyKzl1+IW0aGrRvAA8k0Y3NxFhKigbPjOJ4mCKnWEcNX9HQS3gshflcxq8YKhadabGUVfdwjCr6Q== + dependencies: + "@jest/create-cache-key-function" "^27.4.2" + jsonc-parser "^3.2.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" @@ -605,10 +790,25 @@ request "^2.88.0" webfinger "^0.4.2" -"@tootallnate/once@1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" - integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== +"@tsconfig/node10@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" + integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" + integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== "@types/accepts@*": version "1.3.5" @@ -617,10 +817,10 @@ 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== +"@types/babel__core@^7.1.14": + version "7.1.20" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.20.tgz#e168cdd612c92a2d335029ed62ac94c95b362359" + integrity sha512-PVb6Bg2QuscZ30FvOU7z4guG6c926D9YRvOxEaelzndpMsvP+YM74Q/dAFASpg2l6+XLalxSGxcq/lrgYWZtyQ== dependencies: "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" @@ -643,17 +843,17 @@ "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": - version "7.17.1" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.17.1.tgz#1a0e73e8c28c7e832656db372b779bfd2ef37314" - integrity sha512-kVzjari1s2YVi77D3w1yuvohV2idweYXMCDzqBiVNN63TcDWrIlTVOYpqVrvbbyOE/IyzBoTKF0fdnLPEORFxA== +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.18.3" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.18.3.tgz#dfc508a85781e5698d5b33443416b6268c4b3e8d" + integrity sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w== 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== + version "3.5.38" + resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.38.tgz#7a671e66750ccd21c9fc9d264d0e1e5330bc9908" + integrity sha512-yR/Kxc0dd4FfwtEoLZMoqJbM/VE/W7hXn/MIjb+axcwag0iFmSPK7OBUZq1YWLynJUoWQkfUrI7T0HDqGApNSg== "@types/body-parser@*": version "1.19.2" @@ -663,6 +863,11 @@ "@types/connect" "*" "@types/node" "*" +"@types/chance@1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@types/chance/-/chance-1.1.3.tgz#d19fe9391288d60fdccd87632bfc9ab2b4523fea" + integrity sha512-X6c6ghhe4/sQh4XzcZWSFaTAUOda38GQHmq9BUanYkOE/EO7ZrkazwKmtsj3xzTjkLWmwULE++23g3d3CCWaWw== + "@types/connect@*": version "3.4.35" resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" @@ -692,29 +897,29 @@ 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== +"@types/express-serve-static-core@^4.17.31": + version "4.17.32" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.32.tgz#93dda387f5516af616d8d3f05f2c4c79d81e1b82" + integrity sha512-aI5h/VOkxOF2Z1saPy0Zsxs5avets/iaiAJYznQFm5By/pamU31xWKL//epiF4OfUA2qTOc9PV6tCUjhO8wlZA== 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== + version "4.17.15" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.15.tgz#9290e983ec8b054b65a5abccb610411953d417ff" + integrity sha512-Yv0k4bXGOH+8a+7bELd2PqHQsuiANB+A8a4gnQrkRWzrkKlb6KHaVvyXhqs04sVW/OWlbPyYxRgYlIXLfrufMQ== dependencies: "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.18" + "@types/express-serve-static-core" "^4.17.31" "@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" - integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== +"@types/graceful-fs@^4.1.3": + version "4.1.6" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.6.tgz#e14b2576a1c25026b7f02ede1de3b84c3a1efeae" + integrity sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw== dependencies: "@types/node" "*" @@ -724,9 +929,16 @@ 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== + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.1.tgz#20172f9578b225f6c7da63446f56d4ce108d5a65" + integrity sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ== + +"@types/ioredis@4.28.0": + version "4.28.0" + resolved "https://registry.yarnpkg.com/@types/ioredis/-/ioredis-4.28.0.tgz#609b2ea0d91231df2dd7f67dd77436bc72584911" + integrity sha512-HSA/JQivJgV0e+353gvgu6WVoWvGRe0HyHOnAN2AvbVIhUlJBhNnnkP8gEEokrDWrxywrBkwo8NuDZ6TVPL9XA== + dependencies: + "@types/node" "*" "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.4" @@ -767,7 +979,30 @@ dependencies: "@types/koa" "*" +"@types/koa-pino-logger@3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/koa-pino-logger/-/koa-pino-logger-3.0.0.tgz#275d4b000abc14b1928dc2e9ab476c8296a64b6a" + integrity sha512-sP+12JNX01q+nHpCRqkVIuLjaRemQEfDoFg0evpTnjUEI3jUI2ZrOkhQ5coxn3yVm2tedui/2YhlaPn/XrYNWA== + dependencies: + "@types/koa" "*" + "@types/pino" "*" + "@types/pino-http" "*" + "@types/koa@*": + version "2.13.5" + resolved "https://registry.yarnpkg.com/@types/koa/-/koa-2.13.5.tgz#64b3ca4d54e08c0062e89ec666c9f45443b21a61" + integrity sha512-HSUOdzKz3by4fnqagwthW/1w/yJspTgppyyalPVbgZf8jQWvdIXcVW5h2DGtw4zYntOaeRGx49r1hxoPWrD4aA== + dependencies: + "@types/accepts" "*" + "@types/content-disposition" "*" + "@types/cookies" "*" + "@types/http-assert" "*" + "@types/http-errors" "*" + "@types/keygrip" "*" + "@types/koa-compose" "*" + "@types/node" "*" + +"@types/koa@2.13.4": version "2.13.4" resolved "https://registry.yarnpkg.com/@types/koa/-/koa-2.13.4.tgz#10620b3f24a8027ef5cbae88b393d1b31205726b" integrity sha512-dfHYMfU+z/vKtQB7NUrthdAEiSvnLebvBjwHtfFmpZmB7em2N3WVQdHgnFq+xvyVgxW5jKDmjWfLD3lw4g4uTw== @@ -781,27 +1016,15 @@ "@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/mime@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" + integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== "@types/ms@*": version "0.7.31" @@ -817,15 +1040,53 @@ form-data "^3.0.0" "@types/node@*": - version "17.0.41" - resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.41.tgz#1607b2fd3da014ae5d4d1b31bc792a39348dfb9b" - integrity sha512-xA6drNNeqb5YyV5fO3OAEsnXLfO7uF0whiOfPTz5AeDo8KeZFmODKnvwPymMNO8qE/an8pVY/O50tig2SQCrGw== + version "18.11.18" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.18.tgz#8dfb97f0da23c2293e554c5a50d61ef134d7697f" + integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA== "@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/pino-http@*", "@types/pino-http@5.8.1": + version "5.8.1" + resolved "https://registry.yarnpkg.com/@types/pino-http/-/pino-http-5.8.1.tgz#ebb194750ad2f9245c3028b5d2c4e6d64f685ba9" + integrity sha512-A9MW6VCnx5ii7s+Fs5aFIw+aSZcBCpsZ/atpxamu8tTsvWFacxSf2Hrn1Ohn1jkVRB/LiPGOapRXcFawDBnDnA== + dependencies: + "@types/pino" "6.3" + +"@types/pino-pretty@*": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@types/pino-pretty/-/pino-pretty-5.0.0.tgz#aa7a61cfd553b051764acfa0a49872f7a09a1722" + integrity sha512-N1uzqSzioqz8R3AkDbSJwcfDWeI3YMPNapSQQhnB2ISU4NYgUIcAh+hYT5ygqBM+klX4htpEhXMmoJv3J7GrdA== + dependencies: + pino-pretty "*" + +"@types/pino-std-serializers@*": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/pino-std-serializers/-/pino-std-serializers-4.0.0.tgz#1e28b80b554c8222858e99a4e0fc77fd070e10e8" + integrity sha512-gXfUZx2xIBbFYozGms53fT0nvkacx/+62c8iTxrEqH5PkIGAQvDbXg2774VWOycMPbqn5YJBQ3BMsg4Li3dWbg== + dependencies: + pino-std-serializers "*" + +"@types/pino@*": + version "7.0.5" + resolved "https://registry.yarnpkg.com/@types/pino/-/pino-7.0.5.tgz#1c84a81b924a6a9e263dbb581dffdbad7a3c60c4" + integrity sha512-wKoab31pknvILkxAF8ss+v9iNyhw5Iu/0jLtRkUD74cNfOOLJNnqfFKAv0r7wVaTQxRZtWrMpGfShwwBjOcgcg== + dependencies: + pino "*" + +"@types/pino@6.3": + version "6.3.12" + resolved "https://registry.yarnpkg.com/@types/pino/-/pino-6.3.12.tgz#4425db6ced806109c3df957100cba9dfcd73c228" + integrity sha512-dsLRTq8/4UtVSpJgl9aeqHvbh6pzdmjYD3C092SYgLD2TyoCqHpTJk6vp8DvCTGGc7iowZ2MoiYiVUUCcu7muw== + dependencies: + "@types/node" "*" + "@types/pino-pretty" "*" + "@types/pino-std-serializers" "*" + sonic-boom "^2.1.0" + "@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" @@ -911,9 +1172,9 @@ "@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== + version "7.3.0" + resolved "https://registry.yarnpkg.com/@types/pouchdb-find/-/pouchdb-find-7.3.0.tgz#b917030e9f4bf6e56bf8c3b9fe4b2a25e989009a" + integrity sha512-sFPli5tBjGX9UfXioik1jUzPdcN84eV82n0lmEFuoPepWqkLjQcyri0eOa++HYOaNPyMDhKFBqEALEZivK2dRg== dependencies: "@types/pouchdb-core" "*" @@ -973,9 +1234,9 @@ "@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== + version "2.7.2" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.2.tgz#6c2324641cc4ba050a8c710b2b251b377581fbf0" + integrity sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg== "@types/qs@*": version "6.9.7" @@ -1008,11 +1269,11 @@ 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== + version "1.15.0" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.0.tgz#c7930ff61afb334e121a9da780aac0d9b8f34155" + integrity sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg== dependencies: - "@types/mime" "^1" + "@types/mime" "*" "@types/node" "*" "@types/stack-utils@^2.0.0": @@ -1035,6 +1296,11 @@ dependencies: "@types/node" "*" +"@types/tough-cookie@^4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397" + integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw== + "@types/uuid@8.3.4": version "8.3.4" resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" @@ -1046,23 +1312,25 @@ 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== + version "16.0.5" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.5.tgz#12cc86393985735a283e387936398c2f9e5f88e3" + integrity sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ== dependencies: "@types/yargs-parser" "*" -abab@^2.0.3, abab@^2.0.5: - version "2.0.6" - resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" - integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== +"@types/yargs@^17.0.8": + version "17.0.19" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.19.tgz#8dbecdc9ab48bee0cb74f6e3327de3fa0d0c98ae" + integrity sha512-cAx3qamwaYX9R0fzOIZAlFpo4A+1uBVCxqpKz9D26uTF4srRXaGTTsikQmaotCtNdbhzyUH7ft6p9ktz9s6UNQ== + dependencies: + "@types/yargs-parser" "*" 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: +abort-controller@3.0.0, abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== @@ -1106,30 +1374,17 @@ accepts@^1.3.5, accepts@^1.3.7: 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" - integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== - dependencies: - acorn "^7.1.1" - acorn-walk "^7.1.1" +acorn-walk@^8.1.1: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== -acorn-walk@^7.1.1: - version "7.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" - integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== +acorn@^8.4.1: + version "8.8.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73" + integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA== -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.7.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" - integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== - -agent-base@6: +agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== @@ -1184,15 +1439,10 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== -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== + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== dependencies: normalize-path "^3.0.0" picomatch "^2.0.4" @@ -1210,6 +1460,11 @@ are-we-there-yet@^2.0.0: delegates "^1.0.0" readable-stream "^3.6.0" +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -1234,18 +1489,23 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" 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 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 sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== +atomic-sleep@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" + integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== + +aws-cloudfront-sign@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/aws-cloudfront-sign/-/aws-cloudfront-sign-2.2.0.tgz#3910f5a6d0d90fec07f2b4ef8ab07f3eefb5625d" + integrity sha512-qG+rwZMP3KRTPPbVmWY8DlrT56AkA4iVOeo23vkdK2EXeW/brJFN2haSNKzVz+oYhFMEIzVVloeAcrEzuRkuVQ== + dependencies: + lodash "^3.6.0" + aws-sdk@2.1030.0: version "2.1030.0" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1030.0.tgz#24a856af3d2b8b37c14a8f59974993661c66fd82" @@ -1267,14 +1527,14 @@ aws-sign2@~0.7.0: 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== + version "1.12.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.12.0.tgz#ce1c9d143389679e253b314241ea9aa5cec980d3" + integrity sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg== 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== + version "3.3.1" + resolved "https://registry.yarnpkg.com/axios-retry/-/axios-retry-3.3.1.tgz#47624646138aedefbad2ac32f226f4ee94b6dcab" + integrity sha512-RohAUQTDxBSWLFEnoIG/6bvmy8l3TfpkclgStjl5MDCMBDgapAWCmr1r/9harQfWC8bzLC8job6UcL1A1Yc+/Q== dependencies: "@babel/runtime" "^7.15.4" is-retry-allowed "^2.2.0" @@ -1286,16 +1546,24 @@ axios@0.24.0: 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== +axios@^1.1.3: + version "1.2.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.2.2.tgz#72681724c6e6a43a9fea860fc558127dbe32f9f1" + integrity sha512-bz/J4gS2S3I7mpN/YZfGFTqhXTYzRho8Ay38w2otuuDR322KzFIWm/4W2K6gIwvWaws5n+mnb7D1lN9uD+QH6Q== dependencies: - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" + follow-redirects "^1.15.0" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + +babel-jest@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-28.1.3.tgz#c1187258197c099072156a0a121c11ee1e3917d5" + integrity sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q== + dependencies: + "@jest/transform" "^28.1.3" "@types/babel__core" "^7.1.14" babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^27.5.1" + babel-preset-jest "^28.1.3" chalk "^4.0.0" graceful-fs "^4.2.9" slash "^3.0.0" @@ -1311,14 +1579,14 @@ babel-plugin-istanbul@^6.1.1: istanbul-lib-instrument "^5.0.4" test-exclude "^6.0.0" -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== +babel-plugin-jest-hoist@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz#1952c4d0ea50f2d6d794353762278d1d8cca3fbe" + integrity sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q== dependencies: "@babel/template" "^7.3.3" "@babel/types" "^7.3.3" - "@types/babel__core" "^7.0.0" + "@types/babel__core" "^7.1.14" "@types/babel__traverse" "^7.0.6" babel-preset-current-node-syntax@^1.0.0: @@ -1339,12 +1607,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@^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== +babel-preset-jest@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz#5dfc20b99abed5db994406c2b9ab94c73aaa419d" + integrity sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A== dependencies: - babel-plugin-jest-hoist "^27.5.1" + babel-plugin-jest-hoist "^28.1.3" babel-preset-current-node-syntax "^1.0.0" balanced-match@^1.0.0: @@ -1423,6 +1691,13 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" @@ -1430,21 +1705,15 @@ braces@^3.0.2, braces@~3.0.2: dependencies: fill-range "^7.0.1" -browser-process-hrtime@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" - integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== - -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== +browserslist@^4.21.3: + version "4.21.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.4.tgz#e7496bbc67b9e39dd0f98565feccdcb0d4ff6987" + integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw== dependencies: - caniuse-lite "^1.0.30001349" - electron-to-chromium "^1.4.147" - escalade "^3.1.1" - node-releases "^2.0.5" - picocolors "^1.0.0" + caniuse-lite "^1.0.30001400" + electron-to-chromium "^1.4.251" + node-releases "^2.0.6" + update-browserslist-db "^1.0.9" bs-logger@0.x: version "0.2.6" @@ -1492,6 +1761,29 @@ buffer@^5.5.0, buffer@^5.6.0: base64-js "^1.3.1" ieee754 "^1.1.13" +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + +bull@4.10.1: + version "4.10.1" + resolved "https://registry.yarnpkg.com/bull/-/bull-4.10.1.tgz#f14974b6089358b62b495a2cbf838aadc098e43f" + integrity sha512-Fp21tRPb2EaZPVfmM+ONZKVz2RA+to+zGgaTLyCKt3JMSU8OOBqK8143OQrnGuGpsyE5G+9FevFAGhdZZfQP2g== + dependencies: + cron-parser "^4.2.1" + debuglog "^1.0.0" + get-port "^5.1.1" + ioredis "^4.28.5" + lodash "^4.17.21" + msgpackr "^1.5.2" + p-timeout "^3.2.0" + semver "^7.3.2" + uuid "^8.3.0" + cache-content-type@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-content-type/-/cache-content-type-1.0.1.tgz#035cde2b08ee2129f4a8315ea8f00a00dba1453c" @@ -1513,6 +1805,14 @@ cacheable-request@^6.0.0: normalize-url "^4.1.0" responselike "^1.0.2" +call-bind@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -1528,10 +1828,10 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -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== +caniuse-lite@^1.0.30001400: + version "1.0.30001442" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001442.tgz#40337f1cf3be7c637b061e2f78582dc1daec0614" + integrity sha512-239m03Pqy0hwxYPYR5JwOIxRJfLTWtle9FV8zosfV5pHg+/51uD4nxcUlM8+mWWGfwKtt8lJNHnD3cWw9VZ6ow== caseless@~0.12.0: version "0.12.0" @@ -1555,6 +1855,11 @@ chalk@^4.0.0, chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chance@1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/chance/-/chance-1.1.8.tgz#5d6c2b78c9170bf6eb9df7acdda04363085be909" + integrity sha512-v7fi5Hj2VbR6dJEGRWLmJBA83LJMS47pkAbmROFxHWd9qmE1esHRZW8Clf1Fhzr3rjxnNZVCjOEv/ivFxeIMtg== + char-regex@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" @@ -1596,9 +1901,9 @@ ci-info@^2.0.0: integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== 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== + version "3.7.1" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.7.1.tgz#708a6cdae38915d597afdf3b145f2f8e1ff55f3f" + integrity sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w== cjs-module-lexer@^1.0.0: version "1.2.2" @@ -1610,13 +1915,13 @@ cli-boxes@^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== +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== dependencies: string-width "^4.2.0" - strip-ansi "^6.0.0" + strip-ansi "^6.0.1" wrap-ansi "^7.0.0" clone-buffer@1.0.0: @@ -1625,16 +1930,16 @@ clone-buffer@1.0.0: integrity sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g== 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== + version "1.0.3" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.3.tgz#af2032aa47816399cf5f0a1d0db902f517abb8c3" + integrity sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA== dependencies: mimic-response "^1.0.0" cluster-key-slot@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d" - integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw== + version "1.1.2" + resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac" + integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA== co@^4.6.0: version "4.6.0" @@ -1675,6 +1980,11 @@ color-support@^1.1.2: resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== +colorette@^2.0.7: + version "2.0.19" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" + integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== + combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -1722,19 +2032,9 @@ content-type@^1.0.4: 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" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" - integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== - dependencies: - safe-buffer "~5.1.1" - -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" + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== cookies@~0.8.0: version "0.8.0" @@ -1754,6 +2054,25 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== +correlation-id@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/correlation-id/-/correlation-id-4.0.0.tgz#c1d3038e5f30d7bfeae5728ff96f27a7506bc2c0" + integrity sha512-WvXtJBlovvOBKqTz/YwWP2gm6CXJZJArfGimp9s/ehmhJMPFbmnPMQe3K60Q9idGNixMvKojMjleyDhZEFdHfg== + dependencies: + uuid "^8.3.1" + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +cron-parser@^4.2.1: + version "4.7.1" + resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-4.7.1.tgz#1e325a6a18e797a634ada1e2599ece0b6b5ed177" + integrity sha512-WguFaoQ0hQ61SgsCZLHUcNbAvlK0lypKXu62ARguefYmjzaOXIVRNrAmyXzabTwUn4sQvQLkk6bjH+ipGfw8bA== + dependencies: + luxon "^3.2.1" + cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -1773,23 +2092,6 @@ crypto-random-string@^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" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" - integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== - -cssom@~0.3.6: - version "0.3.8" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" - integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== - -cssstyle@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" - integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== - dependencies: - cssom "~0.3.6" - dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -1797,14 +2099,10 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -data-urls@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" - integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== - dependencies: - abab "^2.0.3" - whatwg-mimetype "^2.3.0" - whatwg-url "^8.0.0" +dateformat@^4.6.3: + version "4.6.3" + resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.6.3.tgz#556fa6497e5217fedb78821424f8a1c22fa3f4b5" + integrity sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA== debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: version "4.3.4" @@ -1820,17 +2118,10 @@ debug@^3.2.7: 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" - -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== +debuglog@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" + integrity sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw== decompress-response@^3.3.0: version "3.3.0" @@ -1854,11 +2145,6 @@ deep-extend@^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" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" - integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== - deepmerge@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" @@ -1892,7 +2178,7 @@ denque@^1.1.0: 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: +depd@^1.1.0, 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== @@ -1922,12 +2208,15 @@ diff-sequences@^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" - resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" - integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg== - dependencies: - webidl-conversions "^5.0.0" +diff-sequences@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-28.1.1.tgz#9989dc731266dc2903457a70e996f3a041913ac6" + integrity sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== dot-prop@^5.2.0: version "5.3.0" @@ -1947,9 +2236,9 @@ double-ended-queue@2.1.0-0: 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== + version "0.1.5" + resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.5.tgz#0b5e4d7bad5de8901ea4440624c8e1d20099217e" + integrity sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA== ecc-jsbn@~0.1.1: version "0.1.2" @@ -1971,10 +2260,10 @@ ee-first@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.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== +electron-to-chromium@^1.4.251: + version "1.4.284" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592" + integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA== emitter-listener@1.1.2: version "1.1.2" @@ -1983,10 +2272,10 @@ emitter-listener@1.1.2: dependencies: shimmer "^1.2.0" -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== +emittery@^0.10.2: + version "0.10.2" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.10.2.tgz#902eec8aedb8c41938c46e9385e9db7e03182933" + integrity sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw== emoji-regex@^8.0.0: version "8.0.0" @@ -2036,11 +2325,6 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -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" @@ -2066,33 +2350,11 @@ escape-string-regexp@^2.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== -escodegen@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" - integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== - dependencies: - esprima "^4.0.1" - estraverse "^5.2.0" - esutils "^2.0.2" - optionator "^0.8.1" - optionalDependencies: - source-map "~0.6.1" - -esprima@^4.0.0, esprima@^4.0.1: +esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - event-target-shim@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" @@ -2103,6 +2365,11 @@ events@1.1.1: resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" integrity sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw== +events@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + execa@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" @@ -2123,15 +2390,16 @@ exit@^0.1.2: resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== -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== +expect@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/expect/-/expect-28.1.3.tgz#90a7c1a124f1824133dd4533cce2d2bdcb6603ec" + integrity sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g== dependencies: - "@jest/types" "^27.5.1" - jest-get-type "^27.5.1" - jest-matcher-utils "^27.5.1" - jest-message-util "^27.5.1" + "@jest/expect-utils" "^28.1.3" + jest-get-type "^28.0.2" + jest-matcher-utils "^28.1.3" + jest-message-util "^28.1.3" + jest-util "^28.1.3" extend@~3.0.2: version "3.0.2" @@ -2148,6 +2416,11 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== +fast-copy@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/fast-copy/-/fast-copy-3.0.0.tgz#875ebf33b13948ae012b6e51d33da5e6e7571ab8" + integrity sha512-4HzS+9pQ5Yxtv13Lhs1Z1unMXamBdn5nA4bEi1abYpDNSpSp7ODYQ1KPMF6nTatfEzgH6/zPvXKU1zvHiUjWlA== + 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" @@ -2158,15 +2431,20 @@ fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fast-levenshtein@~2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-redact@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.1.2.tgz#d58e69e9084ce9fa4c1a6fa98a3e1ecf5d7839aa" + integrity sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw== + +fast-safe-stringify@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" + integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== fb-watchman@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" - integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== dependencies: bser "2.1.1" @@ -2213,10 +2491,10 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" -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== +follow-redirects@^1.14.4, follow-redirects@^1.15.0: + version "1.15.2" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" + integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== forever-agent@~0.6.1: version "0.6.1" @@ -2232,6 +2510,15 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + 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" @@ -2303,11 +2590,25 @@ get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +get-intrinsic@^1.0.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" + integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.3" + get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== +get-port@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193" + integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ== + get-stream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" @@ -2341,7 +2642,7 @@ glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" -glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: +glob@^7.1.3, glob@^7.1.4: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -2353,10 +2654,21 @@ glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^8.0.0: + version "8.0.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.0.3.tgz#415c6eb2deed9e502c68fa44a272e6da6eeca42e" + integrity sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.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== + version "3.0.1" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.1.tgz#0c488971f066baceda21447aecb1a8b911d22485" + integrity sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA== dependencies: ini "2.0.0" @@ -2365,32 +2677,6 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -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 sha512-KM54Y9GhdAzfXUHmWEoYmaOykSLuMG7W4HvVLYqyogxOyE6px8oSS8W13ngqW0oDGZ915GFW3V6OM6+qcdvPOA== - dependencies: - gtoken "^1.2.1" - jws "^3.1.4" - lodash.noop "^3.0.1" - request "^2.74.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 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 sha512-5czmF7xkIlJKc1+/+5tltrI1skoR3HKtkDOld9rk+DOucTpZRjOhCoJzoSjxB3M8rP2tEb1VIr1TPyzR3V2PUQ== - dependencies: - async "~2.1.4" - google-auth-library "~0.10.0" - string-template "~1.0.0" - got@^9.6.0: version "9.6.0" resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" @@ -2413,16 +2699,6 @@ graceful-fs@^4.1.2, graceful-fs@^4.2.9: 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.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.4.1" - request "^2.72.0" - har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" @@ -2446,7 +2722,7 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.0.2: +has-symbols@^1.0.2, has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== @@ -2475,12 +2751,13 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" -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" - integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ== +help-me@^4.0.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/help-me/-/help-me-4.2.0.tgz#50712bfd799ff1854ae1d312c36eafcea85b0563" + integrity sha512-TAOnTB8Tz5Dw8penUuzHVrKNKlCIbwwbHnXraNJxPwf8LRtE2HlM84RYuezMFcwOJmoYOCWVDyJ8TQGxn9PgxA== dependencies: - whatwg-encoding "^1.0.5" + glob "^8.0.0" + readable-stream "^3.6.0" html-escaper@^2.0.0: version "2.0.2" @@ -2496,9 +2773,16 @@ http-assert@^1.3.0: 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== + version "4.1.1" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" + integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== + +http-cookie-agent@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/http-cookie-agent/-/http-cookie-agent-4.0.2.tgz#dcdaae18ed1f7452d81ae4d5cd80b227d6831b69" + integrity sha512-noTmxdH5CuytTnLj/Qv3Z84e/YFq8yLXAw3pqIYZ25Edhb9pQErIAC+ednw40Cic6Le/h9ryph5/TqsvkOaUCw== + dependencies: + agent-base "^6.0.2" http-errors@^1.6.3, http-errors@~1.8.0: version "1.8.1" @@ -2511,15 +2795,6 @@ http-errors@^1.6.3, http-errors@~1.8.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" - integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== - dependencies: - "@tootallnate/once" "1" - agent-base "6" - debug "4" - http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -2542,19 +2817,12 @@ human-signals@^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: - 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: 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: +ieee754@^1.1.13, ieee754@^1.1.4, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -2642,6 +2910,23 @@ ioredis@4.28.0: redis-parser "^3.0.0" standard-as-callback "^2.1.0" +ioredis@^4.28.5: + version "4.28.5" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.28.5.tgz#5c149e6a8d76a7f8fa8a504ffc85b7d5b6797f9f" + integrity sha512-3GYo0GJtLqgNXj4YhrisLaNNvWSNwSS2wS4OELGfGxH8I69+XfNdnmV1AyN+ZqMh0i7eX+SWjrwFKDBDgfBC1A== + dependencies: + cluster-key-slot "^1.1.0" + debug "^4.3.1" + denque "^1.1.0" + lodash.defaults "^4.2.0" + lodash.flatten "^4.4.0" + lodash.isarguments "^3.1.0" + p-map "^2.1.0" + redis-commands "1.7.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^2.1.0" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -2666,10 +2951,10 @@ is-ci@^2.0.0: dependencies: ci-info "^2.0.0" -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== +is-core-module@^2.9.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" + integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== dependencies: has "^1.0.3" @@ -2730,11 +3015,6 @@ is-path-inside@^3.0.2: 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-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" @@ -2781,9 +3061,9 @@ istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== 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== + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== dependencies: "@babel/core" "^7.12.3" "@babel/parser" "^7.14.7" @@ -2810,92 +3090,89 @@ istanbul-lib-source-maps@^4.0.0: source-map "^0.6.1" 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== + version "3.1.5" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.5.tgz#cc9a6ab25cb25659810e4785ed9d9fb742578bae" + integrity sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w== dependencies: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -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== +jest-changed-files@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-28.1.3.tgz#d9aeee6792be3686c47cb988a8eaf82ff4238831" + integrity sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA== dependencies: - "@jest/types" "^27.5.1" execa "^5.0.0" - throat "^6.0.1" + p-limit "^3.1.0" -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== +jest-circus@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-28.1.3.tgz#d14bd11cf8ee1a03d69902dc47b6bd4634ee00e4" + integrity sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow== dependencies: - "@jest/environment" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/environment" "^28.1.3" + "@jest/expect" "^28.1.3" + "@jest/test-result" "^28.1.3" + "@jest/types" "^28.1.3" "@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" + jest-each "^28.1.3" + jest-matcher-utils "^28.1.3" + jest-message-util "^28.1.3" + jest-runtime "^28.1.3" + jest-snapshot "^28.1.3" + jest-util "^28.1.3" + p-limit "^3.1.0" + pretty-format "^28.1.3" 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== +jest-cli@^28.1.1: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-28.1.3.tgz#558b33c577d06de55087b8448d373b9f654e46b2" + integrity sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ== dependencies: - "@jest/core" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/core" "^28.1.3" + "@jest/test-result" "^28.1.3" + "@jest/types" "^28.1.3" chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.9" import-local "^3.0.2" - jest-config "^27.5.1" - jest-util "^27.5.1" - jest-validate "^27.5.1" + jest-config "^28.1.3" + jest-util "^28.1.3" + jest-validate "^28.1.3" prompts "^2.0.1" - yargs "^16.2.0" + yargs "^17.3.1" -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== +jest-config@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-28.1.3.tgz#e315e1f73df3cac31447eed8b8740a477392ec60" + integrity sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ== dependencies: - "@babel/core" "^7.8.0" - "@jest/test-sequencer" "^27.5.1" - "@jest/types" "^27.5.1" - babel-jest "^27.5.1" + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^28.1.3" + "@jest/types" "^28.1.3" + babel-jest "^28.1.3" chalk "^4.0.0" ci-info "^3.2.0" deepmerge "^4.2.2" - glob "^7.1.1" + glob "^7.1.3" 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" + jest-circus "^28.1.3" + jest-environment-node "^28.1.3" + jest-get-type "^28.0.2" + jest-regex-util "^28.0.2" + jest-resolve "^28.1.3" + jest-runner "^28.1.3" + jest-util "^28.1.3" + jest-validate "^28.1.3" micromatch "^4.0.4" parse-json "^5.2.0" - pretty-format "^27.5.1" + pretty-format "^28.1.3" slash "^3.0.0" strip-json-comments "^3.1.1" @@ -2909,106 +3186,84 @@ jest-diff@^27.5.1: jest-get-type "^27.5.1" pretty-format "^27.5.1" -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== +jest-diff@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-28.1.3.tgz#948a192d86f4e7a64c5264ad4da4877133d8792f" + integrity sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw== + dependencies: + chalk "^4.0.0" + diff-sequences "^28.1.1" + jest-get-type "^28.0.2" + pretty-format "^28.1.3" + +jest-docblock@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-28.1.1.tgz#6f515c3bf841516d82ecd57a62eed9204c2f42a8" + integrity sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA== dependencies: detect-newline "^3.0.0" -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== +jest-each@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-28.1.3.tgz#bdd1516edbe2b1f3569cfdad9acd543040028f81" + integrity sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g== dependencies: - "@jest/types" "^27.5.1" + "@jest/types" "^28.1.3" chalk "^4.0.0" - jest-get-type "^27.5.1" - jest-util "^27.5.1" - pretty-format "^27.5.1" + jest-get-type "^28.0.2" + jest-util "^28.1.3" + pretty-format "^28.1.3" -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== +jest-environment-node@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-28.1.3.tgz#7e74fe40eb645b9d56c0c4b70ca4357faa349be5" + integrity sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A== dependencies: - "@jest/environment" "^27.5.1" - "@jest/fake-timers" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/environment" "^28.1.3" + "@jest/fake-timers" "^28.1.3" + "@jest/types" "^28.1.3" "@types/node" "*" - jest-mock "^27.5.1" - jest-util "^27.5.1" - jsdom "^16.6.0" - -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" "^27.5.1" - "@jest/fake-timers" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/node" "*" - jest-mock "^27.5.1" - jest-util "^27.5.1" + jest-mock "^28.1.3" + jest-util "^28.1.3" 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@^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== +jest-get-type@^28.0.2: + version "28.0.2" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-28.0.2.tgz#34622e628e4fdcd793d46db8a242227901fcf203" + integrity sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA== + +jest-haste-map@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-28.1.3.tgz#abd5451129a38d9841049644f34b034308944e2b" + integrity sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA== dependencies: - "@jest/types" "^27.5.1" - "@types/graceful-fs" "^4.1.2" + "@jest/types" "^28.1.3" + "@types/graceful-fs" "^4.1.3" "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" 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" + jest-regex-util "^28.0.2" + jest-util "^28.1.3" + jest-worker "^28.1.3" micromatch "^4.0.4" - walker "^1.0.7" + walker "^1.0.8" optionalDependencies: fsevents "^2.3.2" -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== +jest-leak-detector@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz#a6685d9b074be99e3adee816ce84fd30795e654d" + integrity sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA== dependencies: - "@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 "^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" - throat "^6.0.1" + jest-get-type "^28.0.2" + pretty-format "^28.1.3" -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 "^27.5.1" - pretty-format "^27.5.1" - -jest-matcher-utils@^27.0.0, jest-matcher-utils@^27.5.1: +jest-matcher-utils@^27.0.0: 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== @@ -3018,209 +3273,212 @@ jest-matcher-utils@^27.0.0, jest-matcher-utils@^27.5.1: jest-get-type "^27.5.1" pretty-format "^27.5.1" -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== +jest-matcher-utils@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz#5a77f1c129dd5ba3b4d7fc20728806c78893146e" + integrity sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw== + dependencies: + chalk "^4.0.0" + jest-diff "^28.1.3" + jest-get-type "^28.0.2" + pretty-format "^28.1.3" + +jest-message-util@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-28.1.3.tgz#232def7f2e333f1eecc90649b5b94b0055e7c43d" + integrity sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g== dependencies: "@babel/code-frame" "^7.12.13" - "@jest/types" "^27.5.1" + "@jest/types" "^28.1.3" "@types/stack-utils" "^2.0.0" chalk "^4.0.0" graceful-fs "^4.2.9" micromatch "^4.0.4" - pretty-format "^27.5.1" + pretty-format "^28.1.3" slash "^3.0.0" stack-utils "^2.0.3" -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== +jest-mock@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-28.1.3.tgz#d4e9b1fc838bea595c77ab73672ebf513ab249da" + integrity sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA== dependencies: - "@jest/types" "^27.5.1" + "@jest/types" "^28.1.3" "@types/node" "*" jest-pnp-resolver@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" - integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== -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-regex-util@^28.0.2: + version "28.0.2" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-28.0.2.tgz#afdc377a3b25fb6e80825adcf76c854e5bf47ead" + integrity sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw== -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== +jest-resolve-dependencies@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.3.tgz#8c65d7583460df7275c6ea2791901fa975c1fe66" + integrity sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA== dependencies: - "@jest/types" "^27.5.1" - jest-regex-util "^27.5.1" - jest-snapshot "^27.5.1" + jest-regex-util "^28.0.2" + jest-snapshot "^28.1.3" -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== +jest-resolve@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-28.1.3.tgz#cfb36100341ddbb061ec781426b3c31eb51aa0a8" + integrity sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ== dependencies: - "@jest/types" "^27.5.1" chalk "^4.0.0" graceful-fs "^4.2.9" - jest-haste-map "^27.5.1" + jest-haste-map "^28.1.3" jest-pnp-resolver "^1.2.2" - jest-util "^27.5.1" - jest-validate "^27.5.1" + jest-util "^28.1.3" + jest-validate "^28.1.3" resolve "^1.20.0" resolve.exports "^1.1.0" slash "^3.0.0" -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== +jest-runner@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-28.1.3.tgz#5eee25febd730b4713a2cdfd76bdd5557840f9a1" + integrity sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA== dependencies: - "@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" + "@jest/console" "^28.1.3" + "@jest/environment" "^28.1.3" + "@jest/test-result" "^28.1.3" + "@jest/transform" "^28.1.3" + "@jest/types" "^28.1.3" "@types/node" "*" chalk "^4.0.0" - emittery "^0.8.1" + emittery "^0.10.2" 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 "^6.0.1" + jest-docblock "^28.1.1" + jest-environment-node "^28.1.3" + jest-haste-map "^28.1.3" + jest-leak-detector "^28.1.3" + jest-message-util "^28.1.3" + jest-resolve "^28.1.3" + jest-runtime "^28.1.3" + jest-util "^28.1.3" + jest-watcher "^28.1.3" + jest-worker "^28.1.3" + p-limit "^3.1.0" + source-map-support "0.5.13" -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== +jest-runtime@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-28.1.3.tgz#a57643458235aa53e8ec7821949e728960d0605f" + integrity sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw== dependencies: - "@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" + "@jest/environment" "^28.1.3" + "@jest/fake-timers" "^28.1.3" + "@jest/globals" "^28.1.3" + "@jest/source-map" "^28.1.2" + "@jest/test-result" "^28.1.3" + "@jest/transform" "^28.1.3" + "@jest/types" "^28.1.3" chalk "^4.0.0" cjs-module-lexer "^1.0.0" collect-v8-coverage "^1.0.0" execa "^5.0.0" glob "^7.1.3" 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" + jest-haste-map "^28.1.3" + jest-message-util "^28.1.3" + jest-mock "^28.1.3" + jest-regex-util "^28.0.2" + jest-resolve "^28.1.3" + jest-snapshot "^28.1.3" + jest-util "^28.1.3" slash "^3.0.0" strip-bom "^4.0.0" -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== +jest-snapshot@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-28.1.3.tgz#17467b3ab8ddb81e2f605db05583d69388fc0668" + integrity sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg== dependencies: - "@types/node" "*" - graceful-fs "^4.2.9" - -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/core" "^7.11.6" "@babel/generator" "^7.7.2" "@babel/plugin-syntax-typescript" "^7.7.2" "@babel/traverse" "^7.7.2" - "@babel/types" "^7.0.0" - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/babel__traverse" "^7.0.4" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^28.1.3" + "@jest/transform" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/babel__traverse" "^7.0.6" "@types/prettier" "^2.1.5" babel-preset-current-node-syntax "^1.0.0" chalk "^4.0.0" - expect "^27.5.1" + expect "^28.1.3" 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" + jest-diff "^28.1.3" + jest-get-type "^28.0.2" + jest-haste-map "^28.1.3" + jest-matcher-utils "^28.1.3" + jest-message-util "^28.1.3" + jest-util "^28.1.3" natural-compare "^1.4.0" - pretty-format "^27.5.1" - semver "^7.3.2" + pretty-format "^28.1.3" + semver "^7.3.5" -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== +jest-util@^28.0.0, jest-util@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-28.1.3.tgz#f4f932aa0074f0679943220ff9cbba7e497028b0" + integrity sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ== dependencies: - "@jest/types" "^27.5.1" + "@jest/types" "^28.1.3" "@types/node" "*" chalk "^4.0.0" ci-info "^3.2.0" graceful-fs "^4.2.9" picomatch "^2.2.3" -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== +jest-validate@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-28.1.3.tgz#e322267fd5e7c64cea4629612c357bbda96229df" + integrity sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA== dependencies: - "@jest/types" "^27.5.1" + "@jest/types" "^28.1.3" camelcase "^6.2.0" chalk "^4.0.0" - jest-get-type "^27.5.1" + jest-get-type "^28.0.2" leven "^3.1.0" - pretty-format "^27.5.1" + pretty-format "^28.1.3" -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== +jest-watcher@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-28.1.3.tgz#c6023a59ba2255e3b4c57179fc94164b3e73abd4" + integrity sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g== dependencies: - "@jest/test-result" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/test-result" "^28.1.3" + "@jest/types" "^28.1.3" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" - jest-util "^27.5.1" + emittery "^0.10.2" + jest-util "^28.1.3" string-length "^4.0.1" -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== +jest-worker@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-28.1.3.tgz#7e3c4ce3fa23d1bb6accb169e7f396f98ed4bb98" + integrity sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g== dependencies: "@types/node" "*" merge-stream "^2.0.0" supports-color "^8.0.0" -jest@27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest/-/jest-27.5.1.tgz#dadf33ba70a779be7a6fc33015843b51494f63fc" - integrity sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ== +jest@28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest/-/jest-28.1.1.tgz#3c39a3a09791e16e9ef283597d24ab19a0df701e" + integrity sha512-qw9YHBnjt6TCbIDMPMpJZqf9E12rh6869iZaN08/vpOGgHJSAaLLUn6H8W3IAEuy34Ls3rct064mZLETkxJ2XA== dependencies: - "@jest/core" "^27.5.1" + "@jest/core" "^28.1.1" + "@jest/types" "^28.1.1" import-local "^3.0.2" - jest-cli "^27.5.1" + jest-cli "^28.1.1" jmespath@0.15.0: version "0.15.0" @@ -3243,6 +3501,11 @@ join-component@^1.1.0: resolved "https://registry.yarnpkg.com/join-component/-/join-component-1.1.0.tgz#b8417b750661a392bee2c2537c68b2a9d4977cd5" integrity sha512-bF7vcQxbODoGK1imE2P9GS9aw4zD0Sd+Hni68IMZLj7zRnquH7dXUmMw9hDI5S/Jzt7q+IyTXN0rSg2GI0IKhQ== +joycon@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03" + integrity sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw== + js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -3261,39 +3524,6 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== -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== - dependencies: - abab "^2.0.5" - acorn "^8.2.4" - acorn-globals "^6.0.0" - cssom "^0.4.4" - cssstyle "^2.3.0" - data-urls "^2.0.0" - decimal.js "^10.2.1" - domexception "^2.0.1" - escodegen "^2.0.0" - form-data "^3.0.0" - html-encoding-sniffer "^2.0.1" - http-proxy-agent "^4.0.1" - https-proxy-agent "^5.0.0" - is-potential-custom-element-name "^1.0.1" - nwsapi "^2.2.0" - parse5 "6.0.1" - saxes "^5.0.1" - symbol-tree "^3.2.4" - tough-cookie "^4.0.0" - w3c-hr-time "^1.0.2" - w3c-xmlserializer "^2.0.0" - webidl-conversions "^6.1.0" - whatwg-encoding "^1.0.5" - whatwg-mimetype "^2.3.0" - whatwg-url "^8.5.0" - ws "^7.4.6" - xml-name-validator "^3.0.0" - jsesc@^2.5.1: version "2.5.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" @@ -3324,12 +3554,27 @@ json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== -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== +json5@^2.2.1, json5@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== -jsonwebtoken@8.5.1, jsonwebtoken@^8.2.0: +jsonc-parser@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" + integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== + +jsonwebtoken@9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz#d0faf9ba1cc3a56255fe49c0961a67e520c1926d" + integrity sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw== + dependencies: + jws "^3.2.2" + lodash "^4.17.21" + ms "^2.1.1" + semver "^7.3.8" + +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== @@ -3364,7 +3609,7 @@ jwa@^1.4.1: ecdsa-sig-formatter "1.0.11" safe-buffer "^5.0.1" -jws@^3.0.0, jws@^3.1.4, jws@^3.2.2: +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== @@ -3372,11 +3617,6 @@ jws@^3.0.0, jws@^3.1.4, jws@^3.2.2: jwa "^1.4.1" safe-buffer "^5.0.1" -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" @@ -3396,26 +3636,11 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -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" @@ -3424,11 +3649,6 @@ koa-convert@^2.0.0: 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" @@ -3436,29 +3656,28 @@ koa-passport@4.1.4: 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== +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.7.1" - debug "~3.1.0" + cookies "~0.8.0" + debug "^4.3.2" delegates "^1.0.0" - depd "^1.1.2" + depd "^2.0.0" destroy "^1.0.4" - error-inject "^1.0.0" + 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 "^1.2.0" - koa-is-json "^1.0.0" + koa-convert "^2.0.0" on-finished "^2.3.0" only "~0.0.2" parseurl "^1.3.2" @@ -3467,9 +3686,9 @@ koa@2.7.0: 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== + version "2.14.1" + resolved "https://registry.yarnpkg.com/koa/-/koa-2.14.1.tgz#defb9589297d8eb1859936e777f3feecfc26925c" + integrity sha512-USJFyZgi2l0wDgqkfD27gL4YGno7TfUkcmOe6UOLFOVuN+J7FwnNu4Dydl4CUQzraM1lBAiGed0M9OVJoT0Kqw== dependencies: accepts "^1.3.5" cache-content-type "^1.0.0" @@ -3596,14 +3815,6 @@ leven@^3.1.0: resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== -levn@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA== - dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" - lie@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e" @@ -3673,11 +3884,6 @@ lodash.memoize@4.x: 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 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" @@ -3688,11 +3894,16 @@ lodash.pick@^4.0.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.14.0, lodash@^4.17.21, lodash@^4.7.0: +lodash@4.17.21, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +lodash@^3.6.0: + version "3.10.1" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" + integrity sha512-9mDDwqVIma6OZX79ZlDACZl8sBm0TEnkf99zV3iMA4GzkIT/9hiqP5mY0HoT1iNLCrKc/R1HByV+yJfRWVJryQ== + 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" @@ -3703,6 +3914,13 @@ lowercase-keys@^2.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -3715,6 +3933,11 @@ ltgt@2.2.1, ltgt@^2.1.2, ltgt@~2.2.0: resolved "https://registry.yarnpkg.com/ltgt/-/ltgt-2.2.1.tgz#f35ca91c493f7b73da0e07495304f17b31f87ee5" integrity sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA== +luxon@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.2.1.tgz#14f1af209188ad61212578ea7e3d518d18cee45f" + integrity sha512-QrwPArQCNLAKGO/C+ZIilgIuDnEnKx5QYODdDtbFaxzsbZcc/a7WFq7MhsVYgRlwawLtvOUESTlfJ+hc/USqPg== + 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" @@ -3722,7 +3945,7 @@ make-dir@^3.0.0, make-dir@^3.1.0: dependencies: semver "^6.0.0" -make-error@1.x: +make-error@1.x, make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== @@ -3795,7 +4018,7 @@ mime-types@^2.1.12, mime-types@^2.1.18, mime-types@~2.1.19, mime-types@~2.1.24, dependencies: mime-db "1.52.0" -mime@^1.3.4, mime@^1.4.1: +mime@^1.3.4: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== @@ -3817,15 +4040,29 @@ minimatch@^3.0.4, minimatch@^3.1.1: dependencies: brace-expansion "^1.1.7" -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== +minimatch@^5.0.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.2.tgz#0939d7d6f0898acbd1508abe534d1929368a8fff" + integrity sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.0, minimist@^1.2.6: + version "1.2.7" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" + integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== 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== + version "3.3.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== + dependencies: + yallist "^4.0.0" + +minipass@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.0.0.tgz#7cebb0f9fa7d56f0c5b17853cbe28838a8dbbd3b" + integrity sha512-g2Uuh2jEKoht+zvO6vJqXmYpflPqzRBT+Th2h01DKh5z7wbY/AZ2gCQ78cP70YoHPyFdY30YBV5WxgLOEwOykw== dependencies: yallist "^4.0.0" @@ -3847,11 +4084,6 @@ mkdirp@^1.0.3: 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 sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" @@ -3862,6 +4094,27 @@ ms@^2.1.1, ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +msgpackr-extract@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/msgpackr-extract/-/msgpackr-extract-2.2.0.tgz#4bb749b58d9764cfdc0d91c7977a007b08e8f262" + integrity sha512-0YcvWSv7ZOGl9Od6Y5iJ3XnPww8O7WLcpYMDwX+PAA/uXLDtyw94PJv9GLQV/nnp3cWlDhMoyKZIQLrx33sWog== + dependencies: + node-gyp-build-optional-packages "5.0.3" + optionalDependencies: + "@msgpackr-extract/msgpackr-extract-darwin-arm64" "2.2.0" + "@msgpackr-extract/msgpackr-extract-darwin-x64" "2.2.0" + "@msgpackr-extract/msgpackr-extract-linux-arm" "2.2.0" + "@msgpackr-extract/msgpackr-extract-linux-arm64" "2.2.0" + "@msgpackr-extract/msgpackr-extract-linux-x64" "2.2.0" + "@msgpackr-extract/msgpackr-extract-win32-x64" "2.2.0" + +msgpackr@^1.5.2: + version "1.8.1" + resolved "https://registry.yarnpkg.com/msgpackr/-/msgpackr-1.8.1.tgz#2298aed8a14f83e99df77d344cbda3e436f29b5b" + integrity sha512-05fT4J8ZqjYlR4QcRDIhLCYKUOHXk7C/xa62GzMKj74l3up9k2QZ3LgFc6qWdsPHl91QA2WLWqWc8b8t7GLNNw== + optionalDependencies: + msgpackr-extract "^2.2.0" + napi-macros@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/napi-macros/-/napi-macros-2.0.0.tgz#2b6bae421e7b96eb687aa6c77a7858640670001b" @@ -3887,6 +4140,11 @@ negotiator@0.6.3: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== +node-abort-controller@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.0.1.tgz#f91fa50b1dee3f909afabb7e261b1e1d6b0cb74e" + integrity sha512-/ujIVxthRs+7q6hsdjHMaj8hRG9NuWmwrz+JdRwZ14jdFoKSkm+vDsCbF9PLpnSqjaWQJuTmVtcWHNLr+vrOFw== + 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" @@ -3904,10 +4162,10 @@ node-fetch@2.6.7, node-fetch@^2.6.7: dependencies: whatwg-url "^5.0.0" -node-forge@^0.7.1: - 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-optional-packages@5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.3.tgz#92a89d400352c44ad3975010368072b41ad66c17" + integrity sha512-k75jcVzk5wnnc/FMxsf4udAoTEUv2jY3ycfdSd3yWu6Cnd1oee6/CfZJApyscA4FJOmdoixWwiwOyf16RzD5JA== node-gyp-build@~4.1.0: version "4.1.1" @@ -3920,9 +4178,9 @@ node-int64@^0.4.0: integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== node-mocks-http@^1.11.0: - 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== + version "1.12.1" + resolved "https://registry.yarnpkg.com/node-mocks-http/-/node-mocks-http-1.12.1.tgz#838e176019daf177caff6bb8534e3a32646e7531" + integrity sha512-jrA7Sn3qI6GsHgWtUW3gMj0vO6Yz0nJjzg3jRZYjcfj4tzi8oWPauDK1qHVJoAxTbwuDHF1JiM9GISZ/ocI/ig== dependencies: accepts "^1.3.7" content-disposition "^0.5.3" @@ -3935,10 +4193,10 @@ node-mocks-http@^1.11.0: range-parser "^1.2.0" type-is "^1.6.18" -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== +node-releases@^2.0.6: + version "2.0.8" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.8.tgz#0f349cdc8fcfa39a92ac0be9bc48b7706292b9ae" + integrity sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A== nodemon@2.0.16: version "2.0.16" @@ -3997,11 +4255,6 @@ npmlog@^5.0.1: 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.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" @@ -4017,6 +4270,16 @@ object-assign@^4.1.1: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== +object-inspect@^1.9.0: + version "1.12.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" + integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== + +on-exit-leak-free@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz#5c703c968f7e7f851885f6459bf8a8a57edc9cc4" + integrity sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w== + on-finished@^2.3.0: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" @@ -4043,18 +4306,6 @@ only@~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" - integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== - dependencies: - deep-is "~0.1.3" - fast-levenshtein "~2.0.6" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - word-wrap "~1.2.3" - os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -4065,6 +4316,11 @@ p-cancelable@^1.0.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== + p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -4072,6 +4328,13 @@ p-limit@^2.2.0: dependencies: p-try "^2.0.0" +p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + p-locate@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" @@ -4084,6 +4347,13 @@ p-map@^2.1.0: resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== +p-timeout@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" + integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== + dependencies: + p-finally "^1.0.0" + p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" @@ -4109,24 +4379,11 @@ parse-json@^5.2.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" -parse5@6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" - integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== - 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: - version "1.0.2" - resolved "https://registry.yarnpkg.com/passport-google-auth/-/passport-google-auth-1.0.2.tgz#8b300b5aa442ef433de1d832ed3112877d0b2938" - integrity sha512-cfAqna6jZLyMEwUdd4PIwAh2mQKQVEDAaRIaom1pG6h4x4Gwjllf/Jflt3TkR1Sen5Rkvr3l7kSXCWE1EKkh8g== - dependencies: - googleapis "^16.0.0" - passport-strategy "1.x" - 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" @@ -4189,7 +4446,7 @@ passport-oauth2@1.x.x: uid2 "0.0.x" utils-merge "1.x.x" -passport-strategy@1.x, passport-strategy@1.x.x, passport-strategy@^1.0.0: +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 sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA== @@ -4242,6 +4499,56 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +pino-abstract-transport@^1.0.0, pino-abstract-transport@v1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.0.0.tgz#cc0d6955fffcadb91b7b49ef220a6cc111d48bb3" + integrity sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA== + dependencies: + readable-stream "^4.0.0" + split2 "^4.0.0" + +pino-pretty@*: + version "9.1.1" + resolved "https://registry.yarnpkg.com/pino-pretty/-/pino-pretty-9.1.1.tgz#e7d64c1db98266ca428ab56567b844ba780cd0e1" + integrity sha512-iJrnjgR4FWQIXZkUF48oNgoRI9BpyMhaEmihonHeCnZ6F50ZHAS4YGfGBT/ZVNsPmd+hzkIPGzjKdY08+/yAXw== + dependencies: + colorette "^2.0.7" + dateformat "^4.6.3" + fast-copy "^3.0.0" + fast-safe-stringify "^2.1.1" + help-me "^4.0.1" + joycon "^3.1.1" + minimist "^1.2.6" + on-exit-leak-free "^2.1.0" + pino-abstract-transport "^1.0.0" + pump "^3.0.0" + readable-stream "^4.0.0" + secure-json-parse "^2.4.0" + sonic-boom "^3.0.0" + strip-json-comments "^3.1.1" + +pino-std-serializers@*, pino-std-serializers@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-6.1.0.tgz#307490fd426eefc95e06067e85d8558603e8e844" + integrity sha512-KO0m2f1HkrPe9S0ldjx7za9BJjeHqBku5Ch8JyxETxT8dEFGz1PwgrHaOQupVYitpzbFSYm7nnljxD8dik2c+g== + +pino@*: + version "8.8.0" + resolved "https://registry.yarnpkg.com/pino/-/pino-8.8.0.tgz#1f0d6695a224aa06afc7ad60f2ccc4772d3b9233" + integrity sha512-cF8iGYeu2ODg2gIwgAHcPrtR63ILJz3f7gkogaHC/TXVVXxZgInmNYiIpDYEwgEkxZti2Se6P2W2DxlBIZe6eQ== + dependencies: + atomic-sleep "^1.0.0" + fast-redact "^3.1.1" + on-exit-leak-free "^2.1.0" + pino-abstract-transport v1.0.0 + pino-std-serializers "^6.0.0" + process-warning "^2.0.0" + quick-format-unescaped "^4.0.3" + real-require "^0.2.0" + safe-stable-stringify "^2.3.1" + sonic-boom "^3.1.0" + thread-stream "^2.0.0" + pirates@^4.0.4: version "4.0.5" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" @@ -4475,11 +4782,6 @@ pouchdb@7.3.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 sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== - prepend-http@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" @@ -4494,11 +4796,31 @@ pretty-format@^27.0.0, pretty-format@^27.5.1: ansi-styles "^5.0.0" react-is "^17.0.1" +pretty-format@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-28.1.3.tgz#c9fba8cedf99ce50963a11b27d982a9ae90970d5" + integrity sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q== + dependencies: + "@jest/schemas" "^28.1.3" + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^18.0.0" + 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== +process-warning@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-2.1.0.tgz#1e60e3bfe8183033bbc1e702c2da74f099422d1a" + integrity sha512-9C20RLxrZU/rFnxWncDkuF6O999NdIf3E1ws4B0ZeY3sRVPzWBMsYDE2lxjxhiXxg464cQTgKUGm8/i6y2YGXg== + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== + prompts@^2.0.1: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" @@ -4507,15 +4829,20 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" 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== + version "1.9.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" + integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== pstree.remy@^1.1.8: version "1.1.8" @@ -4547,6 +4874,13 @@ pupa@^2.1.1: dependencies: escape-goat "^2.0.0" +qs@^6.11.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" + qs@~6.5.2: version "6.5.3" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" @@ -4557,12 +4891,22 @@ querystring@0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g== +querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== + +quick-format-unescaped@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" + integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== + 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: +rc@1.2.8, rc@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== @@ -4577,6 +4921,11 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== +react-is@^18.0.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== + 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" @@ -4596,6 +4945,16 @@ readable-stream@1.1.14, readable-stream@^1.0.27-1: string_decoder "^1.1.1" util-deprecate "^1.0.1" +readable-stream@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.3.0.tgz#0914d0c72db03b316c9733bb3461d64a3cc50cba" + integrity sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ== + dependencies: + abort-controller "^3.0.0" + buffer "^6.0.3" + events "^3.3.0" + process "^0.11.10" + readable-stream@~0.0.2: version "0.0.4" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-0.0.4.tgz#f32d76e3fb863344a548d79923007173665b3b8d" @@ -4626,6 +4985,11 @@ readline-sync@^1.4.9: resolved "https://registry.yarnpkg.com/readline-sync/-/readline-sync-1.4.10.tgz#41df7fbb4b6312d673011594145705bf56d8873b" integrity sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw== +real-require@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.2.0.tgz#209632dea1810be2ae063a6ac084fee7e33fba78" + integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg== + redis-commands@1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.7.0.tgz#15a6fea2d58281e27b1cd1acfb4b293e278c3a89" @@ -4650,17 +5014,17 @@ redlock@4.2.0: dependencies: bluebird "^3.7.2" -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== +regenerator-runtime@^0.13.11: + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== 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== + version "4.2.2" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.2.tgz#f02d49c3668884612ca031419491a13539e21fac" + integrity sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg== dependencies: - rc "^1.2.8" + rc "1.2.8" registry-url@^5.0.0: version "5.1.0" @@ -4674,7 +5038,7 @@ remove-trailing-slash@^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: +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== @@ -4705,6 +5069,11 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" @@ -4723,11 +5092,11 @@ resolve.exports@^1.1.0: integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== resolve@^1.20.0: - version "1.22.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" - integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== + version "1.22.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" + integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== dependencies: - is-core-module "^2.8.1" + is-core-module "^2.9.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" @@ -4755,7 +5124,12 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: +safe-stable-stringify@^2.3.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.2.tgz#ec7b037768098bf65310d1d64370de0dc02353aa" + integrity sha512-gMxvPJYhP0O9n2pvcfYfIuYgbledAOJFcqRThtPRmjscaipiwcwPPKLytpVzMkG2HAN87Qmo2d4PtGiri1dSLA== + +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== @@ -4775,12 +5149,10 @@ sax@>=0.1.1, sax@>=0.6.0: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -saxes@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" - integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== - dependencies: - xmlchars "^2.2.0" +secure-json-parse@^2.4.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-2.7.0.tgz#5a5f9cd6ae47df23dba3151edd06855d47e09862" + integrity sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw== semver-diff@^3.1.1: version "3.1.1" @@ -4789,13 +5161,20 @@ semver-diff@^3.1.1: dependencies: semver "^6.3.0" -semver@7.3.7, semver@7.x, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: +semver@7.3.7: 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@7.x, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.8: + version "7.3.8" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" + integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== + 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" @@ -4833,7 +5212,16 @@ shimmer@^1.2.0: 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, signal-exit@^3.0.3: +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: 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== @@ -4848,24 +5236,33 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -source-map-support@^0.5.6: - 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== +sonic-boom@^2.1.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-2.8.0.tgz#c1def62a77425090e6ad7516aad8eb402e047611" + integrity sha512-kuonw1YOYYNOve5iHdSahXPOK49GqwA+LZhI6Wz/l0rP57iKyXXIHaRagOBHAPmGwJC6od2Z9zgvZ5loSgMlVg== + dependencies: + atomic-sleep "^1.0.0" + +sonic-boom@^3.0.0, sonic-boom@^3.1.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-3.2.1.tgz#972ceab831b5840a08a002fa95a672008bda1c38" + integrity sha512-iITeTHxy3B9FGu8aVdiDXUVAcHMF9Ss0cCsAOo2HfCrmVGT3/DT5oYaeu0M/YKZDlKTvChEyPq0zI9Hf33EX6A== + dependencies: + atomic-sleep "^1.0.0" + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: +source-map@^0.6.0, 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.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" @@ -4883,6 +5280,11 @@ split2@^2.1.0: dependencies: through2 "^2.0.2" +split2@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/split2/-/split2-4.1.0.tgz#101907a24370f85bb782f08adaabe4e281ecf809" + integrity sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ== + sprintf-js@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" @@ -4909,9 +5311,9 @@ sshpk@^1.7.0: tweetnacl "~0.14.0" 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== + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== dependencies: escape-string-regexp "^2.0.0" @@ -4938,11 +5340,6 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -string-template@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/string-template/-/string-template-1.0.0.tgz#9e9f2233dc00f218718ec379a28a5673ecca8b96" - integrity sha512-SLqR3GBUXuoPP5MmYtD7ompvXiG87QjT6lzOszyXjTM86Uu7At7vNnt2xgyTLq5o9T4IxTYFyGxcULqpsmsfdg== - "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" @@ -4978,6 +5375,11 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + strip-bom@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" @@ -5030,9 +5432,9 @@ supports-color@^8.0.0: 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" - integrity sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ== + version "2.3.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624" + integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA== dependencies: has-flag "^4.0.0" supports-color "^7.0.0" @@ -5042,11 +5444,6 @@ supports-preserve-symlinks-flag@^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: version "2.1.1" resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" @@ -5069,13 +5466,13 @@ tar-stream@^2.1.4: 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== + version "6.1.13" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.13.tgz#46e22529000f612180601a6fe0680e7da508847b" + integrity sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" - minipass "^3.0.0" + minipass "^4.0.0" minizlib "^2.1.1" mkdirp "^1.0.3" yallist "^4.0.0" @@ -5097,10 +5494,12 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" -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== +thread-stream@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.2.0.tgz#310c03a253f729094ce5d4638ef5186dfa80a9e8" + integrity sha512-rUkv4/fnb4rqy/gGy7VuqK6wE1+1DOCOWy4RMeaV69ZHMP11tQKZvZSip1yTgrKCMZzEMcCL/bKfHvSfDHx+iQ== + dependencies: + real-require "^0.2.0" through2@3.0.2: version "3.0.2" @@ -5138,7 +5537,7 @@ tmpl@1.0.5: to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== to-readable-stream@^1.0.0: version "1.0.0" @@ -5164,14 +5563,15 @@ touch@^3.1.0: dependencies: nopt "~1.0.10" -"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== +"tough-cookie@^2.3.3 || ^3.0.1 || ^4.0.0", tough-cookie@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874" + integrity sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ== dependencies: psl "^1.1.33" punycode "^2.1.1" - universalify "^0.1.2" + universalify "^0.2.0" + url-parse "^1.5.3" tough-cookie@~2.5.0: version "2.5.0" @@ -5181,31 +5581,52 @@ tough-cookie@~2.5.0: psl "^1.1.28" punycode "^2.1.1" -tr46@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240" - integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw== - dependencies: - punycode "^2.1.1" - tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== -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== +ts-jest@28.0.4: + version "28.0.4" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-28.0.4.tgz#0ab705a60fc4b9f3506f35e26edfa9e9c915c31b" + integrity sha512-S6uRDDdCJBvnZqyGjB4VCnwbQrbgdL8WPeP4jevVSpYsBaeGRQAIS08o3Svav2Ex+oXwLgJ/m7F24TNq62kA1A== dependencies: bs-logger "0.x" fast-json-stable-stringify "2.x" - jest-util "^27.0.0" - json5 "2.x" + jest-util "^28.0.0" + json5 "^2.2.1" lodash.memoize "4.x" make-error "1.x" semver "7.x" - yargs-parser "20.x" + yargs-parser "^20.x" + +ts-node@10.8.1: + version "10.8.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.8.1.tgz#ea2bd3459011b52699d7e88daa55a45a1af4f066" + integrity sha512-Wwsnao4DQoJsN034wePSg5nZiw4YKXf56mPIAeD6wVmiv+RytNSWqc2f3fKvcUoV+Yn2+yocD71VOfQHbmVX4g== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +tsconfig-paths@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.0.0.tgz#1082f5d99fd127b72397eef4809e4dd06d229b64" + integrity sha512-SLBg2GBKlR6bVtMgJJlud/o3waplKtL7skmLkExomIiaAtLGtVsoXIqP3SYdjbcH9lq/KVv7pMZeCBpLYOit6Q== + dependencies: + json5 "^2.2.1" + minimist "^1.2.6" + strip-bom "^3.0.0" tsscmp@1.0.6: version "1.0.6" @@ -5215,21 +5636,14 @@ tsscmp@1.0.6: tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== dependencies: safe-buffer "^5.0.1" tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= - -type-check@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= - dependencies: - prelude-ls "~1.1.2" + integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== type-detect@4.0.8: version "4.0.8" @@ -5283,10 +5697,18 @@ unique-string@^2.0.0: dependencies: 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== +universalify@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" + integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== + +update-browserslist-db@^1.0.9: + version "1.0.10" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" + integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" update-notifier@^5.1.0: version "5.1.0" @@ -5318,14 +5740,22 @@ uri-js@^4.2.2: 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= + integrity sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ== dependencies: prepend-http "^2.0.0" +url-parse@^1.5.3: + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + url@0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" - integrity sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ= + integrity sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ== dependencies: punycode "1.3.2" querystring "0.2.0" @@ -5333,12 +5763,12 @@ url@0.10.3: 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= + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== utils-merge@1.x.x: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== uuid@3.3.2: version "3.3.2" @@ -5350,7 +5780,7 @@ uuid@8.1.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.1.0.tgz#6f1536eb43249f473abc6bd58ff983da1ca30d8d" integrity sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg== -uuid@8.3.2, uuid@^8.3.2: +uuid@8.3.2, uuid@^8.3.0, uuid@^8.3.1, 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== @@ -5360,24 +5790,29 @@ uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -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== +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +v8-to-istanbul@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz#b6f994b0b5d4ef255e17a0d17dc444a9f5132fa4" + integrity sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w== dependencies: + "@jridgewell/trace-mapping" "^0.3.12" "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^1.6.0" - source-map "^0.7.3" vary@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== verror@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + integrity sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw== dependencies: assert-plus "^1.0.0" core-util-is "1.0.2" @@ -5386,23 +5821,9 @@ verror@1.10.0: vuvuzela@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/vuvuzela/-/vuvuzela-1.0.3.tgz#3be145e58271c73ca55279dd851f12a682114b0b" - integrity sha1-O+FF5YJxxzylUnndhR8SpoIRSws= + integrity sha512-Tm7jR1xTzBbPW+6y1tknKiEhz04Wf/1iZkcTJjSFcpNko43+dFW6+OOeQe9taJIug3NdfUAjFKgUSyQrIKaDvQ== -w3c-hr-time@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" - integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== - dependencies: - browser-process-hrtime "^1.0.0" - -w3c-xmlserializer@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" - integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA== - dependencies: - xml-name-validator "^3.0.0" - -walker@^1.0.7: +walker@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== @@ -5412,7 +5833,7 @@ walker@^1.0.7: webfinger@^0.4.2: version "0.4.2" resolved "https://registry.yarnpkg.com/webfinger/-/webfinger-0.4.2.tgz#3477a6d97799461896039fcffc650b73468ee76d" - integrity sha1-NHem2XeZRhiWA5/P/GULc0aO520= + integrity sha512-PvvQ/k74HkC3q5G7bGu4VYeKDt3ePZMzT5qFPtEnOL8eyIU1/06OtDn9X5vlkQ23BlegA3eN89rDLiYUife3xQ== dependencies: step "0.0.x" xml2js "0.1.x" @@ -5420,47 +5841,16 @@ webfinger@^0.4.2: webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= - -webidl-conversions@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" - integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== - -webidl-conversions@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" - integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== - -whatwg-encoding@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" - integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== - dependencies: - iconv-lite "0.4.24" - -whatwg-mimetype@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" - integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== dependencies: tr46 "~0.0.3" webidl-conversions "^3.0.0" -whatwg-url@^8.0.0, whatwg-url@^8.5.0: - version "8.7.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" - integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg== - dependencies: - lodash "^4.7.0" - tr46 "^2.1.0" - webidl-conversions "^6.1.0" - which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" @@ -5482,11 +5872,6 @@ widest-line@^3.1.0: 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@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -5499,7 +5884,7 @@ wrap-ansi@^7.0.0: wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== write-file-atomic@^3.0.0: version "3.0.3" @@ -5511,32 +5896,30 @@ write-file-atomic@^3.0.0: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" +write-file-atomic@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + write-stream@~0.4.3: version "0.4.3" resolved "https://registry.yarnpkg.com/write-stream/-/write-stream-0.4.3.tgz#83cc8c0347d0af6057a93862b4e3ae01de5c81c1" - integrity sha1-g8yMA0fQr2BXqThitOOuAd5cgcE= + integrity sha512-IJrvkhbAnj89W/GAVdVgbnPiVw5Ntg/B4tc/MUCIEwj/g6JIww1DWJyB/yBMT3yw2/TkT6IUZ0+IYef3flEw8A== dependencies: readable-stream "~0.0.2" -ws@^7.4.6: - 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" - resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" - integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== - xml2js@0.1.x: version "0.1.14" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.1.14.tgz#5274e67f5a64c5f92974cd85139e0332adc6b90c" - integrity sha1-UnTmf1pkxfkpdM2FE54DMq3GuQw= + integrity sha512-pbdws4PPPNc1HPluSUKamY4GWMk592K7qwcj6BExbVOhhubub8+pMda/ql68b6L3luZs/OGjGSB5goV7SnmgnA== dependencies: sax ">=0.1.1" @@ -5551,12 +5934,7 @@ xml2js@0.4.19: xmlbuilder@~9.0.1: version "9.0.7" resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" - integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= - -xmlchars@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" - integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + integrity sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ== xtend@^4.0.2, xtend@~4.0.0, xtend@~4.0.1: version "4.0.2" @@ -5568,35 +5946,55 @@ y18n@^5.0.5: resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + 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@20.x, yargs-parser@^20.2.2: +yargs-parser@^20.x: 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@^16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.3.1: + version "17.6.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.6.2.tgz#2e23f2944e976339a1ee00f18c77fedee8332541" + integrity sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw== dependencies: - cliui "^7.0.2" + cliui "^8.0.1" escalade "^3.1.1" get-caller-file "^2.0.5" require-directory "^2.1.1" - string-width "^4.2.0" + string-width "^4.2.3" y18n "^5.0.5" - yargs-parser "^20.2.2" + yargs-parser "^21.1.1" ylru@^1.2.0: version "1.3.2" resolved "https://registry.yarnpkg.com/ylru/-/ylru-1.3.2.tgz#0de48017473275a4cbdfc83a1eaf67c01af8a785" integrity sha512-RXRJzMiK6U2ye0BlGGZnmpwJDPgakn6aNQ0A7gHRbD4I0uvK4TW6UqkK1V0pp9jskjJBAXd3dRrbzWkqJ+6cxA== +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + zlib@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/zlib/-/zlib-1.0.5.tgz#6e7c972fc371c645a6afb03ab14769def114fcc0" - integrity sha1-bnyXL8NxxkWmr7A6sUdp3vEU/MA= + integrity sha512-40fpE2II+Cd3k8HWTWONfeKE2jL+P42iWJ1zzps5W51qcTsOUKM5Q5m2PFb0CLxlmFAaUuUdJGc3OfZy947v0w== diff --git a/packages/bbui/package.json b/packages/bbui/package.json index 7c2f1d017a..073f57f094 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": "2.0.22", + "version": "2.3.16", "license": "MPL-2.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", @@ -37,53 +37,57 @@ "dist" ], "dependencies": { - "@adobe/spectrum-css-workflow-icons": "^1.2.1", - "@budibase/string-templates": "^2.0.22", - "@spectrum-css/actionbutton": "^1.0.1", - "@spectrum-css/actiongroup": "^1.0.1", - "@spectrum-css/avatar": "^3.0.2", - "@spectrum-css/button": "^3.0.1", - "@spectrum-css/buttongroup": "^3.0.2", - "@spectrum-css/checkbox": "^3.0.2", - "@spectrum-css/dialog": "^3.0.1", - "@spectrum-css/divider": "^1.0.3", - "@spectrum-css/dropzone": "^3.0.2", - "@spectrum-css/fieldgroup": "^3.0.2", - "@spectrum-css/fieldlabel": "^3.0.1", - "@spectrum-css/icon": "^3.0.1", - "@spectrum-css/illustratedmessage": "^3.0.2", - "@spectrum-css/inlinealert": "^2.0.1", - "@spectrum-css/inputgroup": "^3.0.2", - "@spectrum-css/label": "^2.0.10", - "@spectrum-css/link": "^3.1.1", - "@spectrum-css/menu": "^3.0.1", - "@spectrum-css/modal": "^3.0.1", - "@spectrum-css/pagination": "^3.0.3", - "@spectrum-css/picker": "^1.0.1", - "@spectrum-css/popover": "^3.0.1", - "@spectrum-css/progressbar": "^1.0.2", - "@spectrum-css/progresscircle": "^1.0.2", - "@spectrum-css/radio": "^3.0.2", - "@spectrum-css/search": "^3.0.2", - "@spectrum-css/sidenav": "^3.0.2", + "@adobe/spectrum-css-workflow-icons": "1.2.1", + "@budibase/string-templates": "^2.3.16", + "@spectrum-css/accordion": "3.0.24", + "@spectrum-css/actionbutton": "1.0.1", + "@spectrum-css/actiongroup": "1.0.1", + "@spectrum-css/avatar": "3.0.2", + "@spectrum-css/button": "3.0.1", + "@spectrum-css/buttongroup": "3.0.2", + "@spectrum-css/checkbox": "3.0.2", + "@spectrum-css/dialog": "3.0.1", + "@spectrum-css/divider": "1.0.3", + "@spectrum-css/dropzone": "3.0.2", + "@spectrum-css/fieldgroup": "3.0.2", + "@spectrum-css/fieldlabel": "3.0.1", + "@spectrum-css/icon": "3.0.1", + "@spectrum-css/illustratedmessage": "3.0.2", + "@spectrum-css/inlinealert": "2.0.1", + "@spectrum-css/inputgroup": "3.0.2", + "@spectrum-css/label": "2.0.10", + "@spectrum-css/link": "3.1.1", + "@spectrum-css/menu": "3.0.1", + "@spectrum-css/modal": "3.0.1", + "@spectrum-css/pagination": "3.0.3", + "@spectrum-css/picker": "1.0.1", + "@spectrum-css/popover": "3.0.1", + "@spectrum-css/progressbar": "1.0.2", + "@spectrum-css/progresscircle": "1.0.2", + "@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.2.12", - "@spectrum-css/tags": "^3.0.2", - "@spectrum-css/textfield": "^3.0.1", - "@spectrum-css/toast": "^3.0.1", - "@spectrum-css/tooltip": "^3.0.3", - "@spectrum-css/treeview": "^3.0.2", - "@spectrum-css/typography": "^3.0.1", - "@spectrum-css/underlay": "^2.0.9", - "@spectrum-css/vars": "^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.2.12", + "@spectrum-css/tags": "3.0.2", + "@spectrum-css/textfield": "3.0.1", + "@spectrum-css/toast": "3.0.1", + "@spectrum-css/tooltip": "3.0.3", + "@spectrum-css/treeview": "3.0.2", + "@spectrum-css/typography": "3.0.1", + "@spectrum-css/underlay": "2.0.9", + "@spectrum-css/vars": "3.0.1", "dayjs": "^1.10.4", "easymde": "^2.16.1", "svelte-flatpickr": "^3.2.3", "svelte-portal": "^1.0.0" }, + "resolutions": { + "loader-utils": "1.4.1" + }, "gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc" } diff --git a/packages/bbui/src/Accordion/Accordion.svelte b/packages/bbui/src/Accordion/Accordion.svelte new file mode 100644 index 0000000000..1c88450c9a --- /dev/null +++ b/packages/bbui/src/Accordion/Accordion.svelte @@ -0,0 +1,58 @@ + + +
+
+

+ + +

+
+ +
+
+
+ + diff --git a/packages/bbui/src/ActionButton/ActionButton.svelte b/packages/bbui/src/ActionButton/ActionButton.svelte index cfc810807e..663128160f 100644 --- a/packages/bbui/src/ActionButton/ActionButton.svelte +++ b/packages/bbui/src/ActionButton/ActionButton.svelte @@ -9,7 +9,6 @@ export let longPressable = false export let disabled = false export let icon = "" - export let dataCy = null export let size = "M" export let active = false export let fullWidth = false @@ -37,7 +36,6 @@ {#if open}
(open = false)} + use:clickOutside={handleOutsideClick} transition:fly|local={{ y: -20, duration: 200 }} class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open" > diff --git a/packages/bbui/src/Form/Core/Multiselect.svelte b/packages/bbui/src/Form/Core/Multiselect.svelte index eb39e39042..d6c4dc23ac 100644 --- a/packages/bbui/src/Form/Core/Multiselect.svelte +++ b/packages/bbui/src/Form/Core/Multiselect.svelte @@ -16,8 +16,10 @@ export let autoWidth = false const dispatch = createEventDispatcher() + $: selectedLookupMap = getSelectedLookupMap(value) $: optionLookupMap = getOptionLookupMap(options) + $: fieldText = getFieldText(value, optionLookupMap, placeholder) $: isOptionSelected = optionValue => selectedLookupMap[optionValue] === true $: toggleOption = makeToggleOption(selectedLookupMap, value) diff --git a/packages/bbui/src/Form/Core/Picker.svelte b/packages/bbui/src/Form/Core/Picker.svelte index cdaf00aded..32cfcf3310 100644 --- a/packages/bbui/src/Form/Core/Picker.svelte +++ b/packages/bbui/src/Form/Core/Picker.svelte @@ -2,12 +2,12 @@ import "@spectrum-css/picker/dist/index-vars.css" import "@spectrum-css/popover/dist/index-vars.css" import "@spectrum-css/menu/dist/index-vars.css" - import { fly } from "svelte/transition" 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" + import Popover from "../../Popover/Popover.svelte" export let id = null export let disabled = false @@ -19,6 +19,7 @@ export let placeholderOption = null export let options = [] export let isOptionSelected = () => false + export let isOptionEnabled = () => true export let onSelectOption = () => {} export let getOptionLabel = option => option export let getOptionValue = option => option @@ -32,7 +33,10 @@ export let sort = false const dispatch = createEventDispatcher() + let searchTerm = null + let button + let popover $: sortedOptions = getSortedOptions(options, getOptionLabel, sort) $: filteredOptions = getFilteredOptions( @@ -41,7 +45,9 @@ getOptionLabel ) - const onClick = () => { + const onClick = e => { + e.preventDefault() + e.stopPropagation() dispatch("click") if (readonly) { return @@ -75,77 +81,119 @@ } -
(open = false)}> - - {#if open} -
- {#if autocomplete} - (searchTerm = event.detail)} - {disabled} - placeholder="Search" - /> + {/if} + + + + (open = false)} + useAnchorWidth={!autoWidth} + maxWidth={autoWidth ? 400 : null} +> +
(open = false)} + > + {#if autocomplete} + (searchTerm = event.detail)} + {disabled} + placeholder="Search" + /> + {/if} +
    + {#if placeholderOption} +
  • onSelectOption(null)} + > + {placeholderOption} + +
  • {/if} -
      - {#if placeholderOption} + {#if filteredOptions.length} + {#each filteredOptions as option, idx}
    • onSelectOption(null)} + on:click={() => onSelectOption(getOptionValue(option, idx))} + class:is-disabled={!isOptionEnabled(option)} > - {placeholderOption} + {#if getOptionIcon(option, idx)} + + + + {/if} + {#if getOptionColour(option, idx)} + + + + {/if} + + {getOptionLabel(option, idx)} +
    • - {/if} - {#if filteredOptions.length} - {#each filteredOptions as option, idx} -
    • onSelectOption(getOptionValue(option, idx))} - > - {#if getOptionIcon(option, idx)} - - - - {/if} - {#if getOptionColour(option, idx)} - - - - {/if} - - {getOptionLabel(option, idx)} - - -
    • - {/each} - {/if} -
    -
- {/if} -
+ {/each} + {/if} + +
+ diff --git a/packages/bbui/src/Form/Core/PickerDropdown.svelte b/packages/bbui/src/Form/Core/PickerDropdown.svelte index 1607876b46..b0a884738f 100644 --- a/packages/bbui/src/Form/Core/PickerDropdown.svelte +++ b/packages/bbui/src/Form/Core/PickerDropdown.svelte @@ -61,6 +61,7 @@ const onPickPrimary = newValue => { dispatch("pickprimary", newValue) primaryOpen = false + dispatch("closed") } const onClearPrimary = () => { @@ -87,6 +88,21 @@ updateValue(event.target.value) } } + + const handlePrimaryOutsideClick = event => { + if (primaryOpen) { + event.stopPropagation() + primaryOpen = false + dispatch("closed") + } + } + + const handleSecondaryOutsideClick = event => { + if (secondaryOpen) { + event.stopPropagation() + secondaryOpen = false + } + }
{#if primaryOpen}
(primaryOpen = false)} + use:clickOutside={handlePrimaryOutsideClick} transition:fly|local={{ y: -20, duration: 200 }} class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open" class:auto-width={autoWidth} @@ -256,7 +272,7 @@ {disabled} class:is-open={secondaryOpen} aria-haspopup="listbox" - on:mousedown={onClickSecondary} + on:click={onClickSecondary} > {#if secondaryFieldIcon} @@ -281,7 +297,7 @@ {#if secondaryOpen}
(secondaryOpen = false)} + use:clickOutside={handleSecondaryOutsideClick} transition:fly|local={{ y: -20, duration: 200 }} class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open" style="width: 30%" diff --git a/packages/bbui/src/Form/Core/RadioGroup.svelte b/packages/bbui/src/Form/Core/RadioGroup.svelte index a3952a9759..f7afc10bbc 100644 --- a/packages/bbui/src/Form/Core/RadioGroup.svelte +++ b/packages/bbui/src/Form/Core/RadioGroup.svelte @@ -11,14 +11,31 @@ export let getOptionLabel = option => option export let getOptionValue = option => option export let getOptionTitle = option => option + export let sort = false const dispatch = createEventDispatcher() const onChange = e => dispatch("change", e.target.value) + + const getSortedOptions = (options, getLabel, sort) => { + if (!options?.length || !Array.isArray(options)) { + return [] + } + if (!sort) { + return options + } + return [...options].sort((a, b) => { + const labelA = getLabel(a) + const labelB = getLabel(b) + return labelA > labelB ? 1 : -1 + }) + } + + $: parsedOptions = getSortedOptions(options, getOptionLabel, sort)
- {#if options && Array.isArray(options)} - {#each options as option} + {#if parsedOptions && Array.isArray(parsedOptions)} + {#each parsedOptions as option}
option export let getOptionIcon = () => null export let getOptionColour = () => null + export let isOptionEnabled export let readonly = false export let quiet = false export let autoWidth = false export let autocomplete = false export let sort = false + const dispatch = createEventDispatcher() + let open = false + $: fieldText = getFieldText(value, options, placeholder) $: fieldIcon = getFieldAttribute(getOptionIcon, value, options) $: fieldColour = getFieldAttribute(getOptionColour, value, options) @@ -66,6 +70,7 @@ {getOptionValue} {getOptionIcon} {getOptionColour} + {isOptionEnabled} {autocomplete} {sort} isPlaceholder={value == null || value === ""} diff --git a/packages/bbui/src/Form/Core/Switch.svelte b/packages/bbui/src/Form/Core/Switch.svelte index 9e45683382..ffbef6b27d 100644 --- a/packages/bbui/src/Form/Core/Switch.svelte +++ b/packages/bbui/src/Form/Core/Switch.svelte @@ -6,7 +6,6 @@ export let id = null export let text = null export let disabled = false - export let dataCy = null const dispatch = createEventDispatcher() const onChange = event => { @@ -16,7 +15,6 @@
@@ -112,8 +112,4 @@ .spectrum-Textfield { width: 100%; } - input:disabled { - color: var(--spectrum-global-color-gray-600) !important; - -webkit-text-fill-color: var(--spectrum-global-color-gray-600) !important; - } diff --git a/packages/bbui/src/Form/DatePicker.svelte b/packages/bbui/src/Form/DatePicker.svelte index a0b102dbe8..04ce8b5467 100644 --- a/packages/bbui/src/Form/DatePicker.svelte +++ b/packages/bbui/src/Form/DatePicker.svelte @@ -14,11 +14,17 @@ export let placeholder = null export let appendTo = undefined export let ignoreTimezones = false - + export let range = false const dispatch = createEventDispatcher() const onChange = e => { - value = e.detail + if (range) { + // Flatpickr cant take two dates and work out what to display, needs to be provided a string. + // Like - "Date1 to Date2". Hence passing in that specifically from the array + value = e?.detail[1] + } else { + value = e.detail + } dispatch("change", e.detail) } @@ -34,6 +40,7 @@ {time24hr} {appendTo} {ignoreTimezones} + {range} on:change={onChange} /> diff --git a/packages/bbui/src/Form/EnvDropdown.svelte b/packages/bbui/src/Form/EnvDropdown.svelte new file mode 100644 index 0000000000..b5db23a870 --- /dev/null +++ b/packages/bbui/src/Form/EnvDropdown.svelte @@ -0,0 +1,50 @@ + + + + + diff --git a/packages/bbui/src/Form/Input.svelte b/packages/bbui/src/Form/Input.svelte index 251b23c86a..d3cb13e731 100644 --- a/packages/bbui/src/Form/Input.svelte +++ b/packages/bbui/src/Form/Input.svelte @@ -13,8 +13,8 @@ export let error = null export let updateOnChange = true export let quiet = false - export let dataCy export let autofocus + export let autocomplete const dispatch = createEventDispatcher() const onChange = e => { @@ -25,7 +25,6 @@ {} export let getSecondaryOptionIcon = () => {} export let quiet = false - export let dataCy export let autofocus export let primaryOptions = [] export let secondaryOptions = [] @@ -98,7 +97,6 @@ diff --git a/packages/bbui/src/Form/Select.svelte b/packages/bbui/src/Form/Select.svelte index 1b68746c5e..69126e648d 100644 --- a/packages/bbui/src/Form/Select.svelte +++ b/packages/bbui/src/Form/Select.svelte @@ -15,6 +15,7 @@ export let getOptionValue = option => extractProperty(option, "value") export let getOptionIcon = option => option?.icon export let getOptionColour = option => option?.colour + export let isOptionEnabled export let quiet = false export let autoWidth = false export let sort = false @@ -49,6 +50,7 @@ {getOptionValue} {getOptionIcon} {getOptionColour} + {isOptionEnabled} on:change={onChange} on:click /> diff --git a/packages/bbui/src/Form/Toggle.svelte b/packages/bbui/src/Form/Toggle.svelte index 37de2836ba..8cc6506651 100644 --- a/packages/bbui/src/Form/Toggle.svelte +++ b/packages/bbui/src/Form/Toggle.svelte @@ -9,7 +9,6 @@ export let text = null export let disabled = false export let error = null - export let dataCy = null const dispatch = createEventDispatcher() const onChange = e => { @@ -19,5 +18,5 @@ - + diff --git a/packages/bbui/src/Icon/Icon.svelte b/packages/bbui/src/Icon/Icon.svelte index f2cae14f0b..8290acd7cc 100644 --- a/packages/bbui/src/Icon/Icon.svelte +++ b/packages/bbui/src/Icon/Icon.svelte @@ -64,7 +64,7 @@ transition: color var(--spectrum-global-animation-duration-100, 130ms); } svg.hoverable:hover { - color: var(--spectrum-alias-icon-color-selected-hover); + color: var(--spectrum-alias-icon-color-selected-hover) !important; cursor: pointer; } diff --git a/packages/bbui/src/Icon/IconAvatar.svelte b/packages/bbui/src/Icon/IconAvatar.svelte index b404cdea9f..add51f5bdc 100644 --- a/packages/bbui/src/Icon/IconAvatar.svelte +++ b/packages/bbui/src/Icon/IconAvatar.svelte @@ -19,6 +19,7 @@ .icon { width: 28px; height: 28px; + flex: 0 0 28px; display: grid; place-items: center; border-radius: 50%; @@ -34,6 +35,7 @@ .icon.size--S { width: 22px; height: 22px; + flex: 0 0 22px; } .icon.size--S :global(.spectrum-Icon) { width: 16px; @@ -46,6 +48,7 @@ .icon.size--L { width: 40px; height: 40px; + flex: 0 0 40px; } .icon.size--L :global(.spectrum-Icon) { width: 28px; diff --git a/packages/bbui/src/IconPicker/IconPicker.svelte b/packages/bbui/src/IconPicker/IconPicker.svelte index 0e71be2c33..2b42da61b1 100644 --- a/packages/bbui/src/IconPicker/IconPicker.svelte +++ b/packages/bbui/src/IconPicker/IconPicker.svelte @@ -50,6 +50,13 @@ dispatch("change", value) open = false } + + const handleOutsideClick = event => { + if (open) { + event.stopPropagation() + open = false + } + }
@@ -64,7 +71,7 @@
{#if open}
(open = false)} + use:clickOutside={handleOutsideClick} transition:fly={{ y: -20, duration: 200 }} class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open" class:spectrum-Popover--align-right={alignRight} diff --git a/packages/bbui/src/InlineAlert/InlineAlert.svelte b/packages/bbui/src/InlineAlert/InlineAlert.svelte index 94ac6b2c2a..57e7296234 100644 --- a/packages/bbui/src/InlineAlert/InlineAlert.svelte +++ b/packages/bbui/src/InlineAlert/InlineAlert.svelte @@ -56,5 +56,6 @@ --spectrum-semantic-positive-icon-color: #2d9d78; --spectrum-semantic-negative-icon-color: #e34850; min-width: 100px; + margin: 0; } diff --git a/packages/bbui/src/Label/Label.svelte b/packages/bbui/src/Label/Label.svelte index 6b3392ce2d..ee6d9adf76 100644 --- a/packages/bbui/src/Label/Label.svelte +++ b/packages/bbui/src/Label/Label.svelte @@ -21,6 +21,7 @@ label { padding: 0; white-space: nowrap; + color: var(--spectrum-global-color-gray-600); } .muted { diff --git a/packages/bbui/src/Layout/Page.svelte b/packages/bbui/src/Layout/Page.svelte index 2996bcc613..01111fda9a 100644 --- a/packages/bbui/src/Layout/Page.svelte +++ b/packages/bbui/src/Layout/Page.svelte @@ -1,32 +1,101 @@ -
- +
+
+
+ +
+
+
+
{ + sidePanelVisble = false + }} + > + +
diff --git a/packages/bbui/src/Link/Link.svelte b/packages/bbui/src/Link/Link.svelte index 3bbfdd8282..ea9496ba72 100644 --- a/packages/bbui/src/Link/Link.svelte +++ b/packages/bbui/src/Link/Link.svelte @@ -1,5 +1,6 @@ dispatch("click") && e.stopPropagation()} {href} {target} {download} diff --git a/packages/bbui/src/List/ListItem.svelte b/packages/bbui/src/List/ListItem.svelte index 40d3c5541c..28015c4c57 100644 --- a/packages/bbui/src/List/ListItem.svelte +++ b/packages/bbui/src/List/ListItem.svelte @@ -30,9 +30,11 @@ {/if}
-
- -
+ {#if $$slots.default} +
+ +
+ {/if}
diff --git a/packages/bbui/src/Popover/Popover.svench b/packages/bbui/src/Popover/Popover.svench index 4d48d7f121..21ca0c66e8 100644 --- a/packages/bbui/src/Popover/Popover.svench +++ b/packages/bbui/src/Popover/Popover.svench @@ -29,7 +29,6 @@ font-size: var(--font-size-m); margin: 0 0 var(--spacing-l) 0; font-weight: 600; - font-family: var(--font-sans); } .input-group-column { diff --git a/packages/bbui/src/SideNavigation/Item.svelte b/packages/bbui/src/SideNavigation/Item.svelte index aa86fc02a8..dab88b05bc 100644 --- a/packages/bbui/src/SideNavigation/Item.svelte +++ b/packages/bbui/src/SideNavigation/Item.svelte @@ -8,7 +8,6 @@ export let icon = "" export let selected = false export let disabled = false - export let dataCy export let badge = "" @@ -17,7 +16,6 @@ class:is-selected={selected} class:is-disabled={disabled} on:click - data-cy={dataCy} > {#if heading} + + diff --git a/packages/bbui/src/bbui.css b/packages/bbui/src/bbui.css index f98f27bf58..ef3483d3df 100644 --- a/packages/bbui/src/bbui.css +++ b/packages/bbui/src/bbui.css @@ -40,12 +40,14 @@ --rounded-medium: 8px; --rounded-large: 16px; - --font-sans: Source Sans Pro, -apple-system, BlinkMacSystemFont, Segoe UI, "Inter", - "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", - "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --font-sans: "Source Sans Pro", -apple-system, BlinkMacSystemFont, Segoe UI, "Inter", + "Helvetica Neue", Arial, "Noto Sans", sans-serif; + --font-accent: "Source Sans Pro", -apple-system, BlinkMacSystemFont, Segoe UI, "Inter", + "Helvetica Neue", Arial, "Noto Sans", sans-serif; --font-serif: "Georgia", Cambria, Times New Roman, Times, serif; --font-mono: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + --spectrum-alias-body-text-font-family: var(--font-sans); font-size: 16px; --font-size-xs: 0.75rem; @@ -89,6 +91,8 @@ --border-light-2: 2px var(--grey-3) solid; --border-blue: 2px var(--blue) solid; --border-transparent: 2px transparent solid; + + --spectrum-alias-text-color-disabled: var(--spectrum-global-color-gray-600); } a { diff --git a/packages/bbui/src/context.js b/packages/bbui/src/context.js index bb95f8b425..919f2140d6 100644 --- a/packages/bbui/src/context.js +++ b/packages/bbui/src/context.js @@ -1,3 +1,4 @@ export default { Modal: "bbui-modal", + PopoverRoot: "bbui-popover-root", } diff --git a/packages/bbui/src/index.js b/packages/bbui/src/index.js index 538a62188f..482226ed88 100644 --- a/packages/bbui/src/index.js +++ b/packages/bbui/src/index.js @@ -4,6 +4,7 @@ import "./bbui.css" import "@spectrum-css/icon/dist/index-vars.css" // Components +export { default as Skeleton } from "./Skeleton/Skeleton.svelte" export { default as Input } from "./Form/Input.svelte" export { default as Stepper } from "./Form/Stepper.svelte" export { default as TextArea } from "./Form/TextArea.svelte" @@ -26,6 +27,7 @@ export { default as RadioGroup } from "./Form/RadioGroup.svelte" export { default as Checkbox } from "./Form/Checkbox.svelte" export { default as InputDropdown } from "./Form/InputDropdown.svelte" export { default as PickerDropdown } from "./Form/PickerDropdown.svelte" +export { default as EnvDropdown } from "./Form/EnvDropdown.svelte" export { default as DetailSummary } from "./DetailSummary/DetailSummary.svelte" export { default as Popover } from "./Popover/Popover.svelte" export { default as ProgressBar } from "./ProgressBar/ProgressBar.svelte" @@ -74,6 +76,7 @@ export { default as ListItem } from "./List/ListItem.svelte" export { default as IconSideNav } from "./IconSideNav/IconSideNav.svelte" export { default as IconSideNavItem } from "./IconSideNav/IconSideNavItem.svelte" export { default as Slider } from "./Form/Slider.svelte" +export { default as Accordion } from "./Accordion/Accordion.svelte" // Renderers export { default as BoldRenderer } from "./Table/BoldRenderer.svelte" @@ -100,3 +103,6 @@ export { banner, BANNER_TYPES } from "./Stores/banner" // Helpers export * as Helpers from "./helpers" + +// Fancy form components +export * from "./FancyForm" diff --git a/packages/bbui/yarn.lock b/packages/bbui/yarn.lock index 6200487df3..16f1feb920 100644 --- a/packages/bbui/yarn.lock +++ b/packages/bbui/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -"@adobe/spectrum-css-workflow-icons@^1.2.1": +"@adobe/spectrum-css-workflow-icons@1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@adobe/spectrum-css-workflow-icons/-/spectrum-css-workflow-icons-1.2.1.tgz#7e2cb3fcfb5c8b12d7275afafbb6ec44913551b4" integrity sha512-uVgekyBXnOVkxp+CUssjN/gefARtudZC8duEn1vm0lBQFwGRZFlDEzU1QC+aIRWCrD1Z8OgRpmBYlSZ7QS003w== @@ -109,139 +109,144 @@ estree-walker "^1.0.1" picomatch "^2.2.2" -"@spectrum-css/actionbutton@^1.0.1": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@spectrum-css/actionbutton/-/actionbutton-1.0.2.tgz#7753a94c64cebecfca6749ef20e37a5ea80c59be" - integrity sha512-laDWk7PCgy2I0AGsMjTmYKkiMVYVoF1B4tffJf4cIp66znTiqPHEbLDh5EDNU88JLTY2bWoCOY4cJxvXk5gERw== +"@spectrum-css/accordion@3.0.24": + version "3.0.24" + resolved "https://registry.yarnpkg.com/@spectrum-css/accordion/-/accordion-3.0.24.tgz#f89066c120c57b0cfc9aba66d60c39fc1cf69f74" + integrity sha512-jNOmUsxmiT3lRLButnN5KKHM94fd+87fjiF8L0c4uRNgJl6ZsBuxPXrM15lV4y1f8D2IACAw01/ZkGRAeaCOFA== -"@spectrum-css/actiongroup@^1.0.1": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@spectrum-css/actiongroup/-/actiongroup-1.0.2.tgz#37f6593c1defdeda682b9e196bcd8cce38c574aa" - integrity sha512-YHKYruXsJteXlkCvfOoQ1cqc3tV7LbmMmbRpyp2vh0ah9CF5ImyGmBRYOOROtZx7VqfDJ5645rdwB2YfJcCBMw== +"@spectrum-css/actionbutton@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/actionbutton/-/actionbutton-1.0.1.tgz#9c75da37ea6915919fb574c74bd60dacc03b6577" + integrity sha512-AUqtyNabHF451Aj9i3xz82TxS5Z6k1dttA68/1hMeU9kbPCSS4P6Viw3vaRGs9CSspuR8xnnhDgrq+F+zMy2Hw== -"@spectrum-css/avatar@^3.0.2": +"@spectrum-css/actiongroup@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/actiongroup/-/actiongroup-1.0.1.tgz#b95b86e7af229e90fe1e70399d8d4b547b4bd31c" + integrity sha512-5Q6uMjzv5BFA2TwGASr/jAtJpTWl26fhWvgGY8kOA0RCSij35l+YJg/FPXf6Nnj2qCOl8DkNycjT9YXJ+bhyVA== + +"@spectrum-css/avatar@3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@spectrum-css/avatar/-/avatar-3.0.2.tgz#4f1826223eae330e64b6d3cc899e9bc2e98dac95" integrity sha512-wEczvSqxttTWSiL3cOvXV/RmGRwSkw2w6+slcHhnf0kb7ovymMM+9oz8vvEpEsSeo5u598bc+7ktrKFpAd6soQ== -"@spectrum-css/button@^3.0.1": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@spectrum-css/button/-/button-3.0.2.tgz#387712641457d3045bcecf77e13854dd59e9c20e" - integrity sha512-sUJuYrrmIL0RCkQpTQ9B5AdoWlu4Y6JatrcOB/OOwdzLbQ9bHw1bHj/y0Ua67hEtLOTjODU+2e5K3UM3KGnMTA== +"@spectrum-css/button@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/button/-/button-3.0.1.tgz#6db8c3e851baecd0f1c2d88fef37d49d01c6e643" + integrity sha512-YXrBtjIYisk4Vaxnp0RiE4gdElQX04P2mc4Pi2GlQ27dJKlHmufYcF+kAqGdtiyK5yjdN/vKRcC8y13aA4rusA== -"@spectrum-css/buttongroup@^3.0.2": +"@spectrum-css/buttongroup@3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@spectrum-css/buttongroup/-/buttongroup-3.0.2.tgz#fd3387973ca3131609e32112de42a1c0400a48d8" integrity sha512-Wu7B4GJ/SAeVHz9SUGAkeIH8pLaZh4t+w2ykSKOPQIRuK2jCBoudkEClVxviNVwqekccf5XLFXg9GpYF1a3Uaw== -"@spectrum-css/checkbox@^3.0.2": +"@spectrum-css/checkbox@3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@spectrum-css/checkbox/-/checkbox-3.0.2.tgz#53ca2fba0d9faa1fead10e7206eb1f6cdcfd6ddd" integrity sha512-hPbGcnm7kJvJS4jp/P/bdaZvbyR1eIE9mteuZqcBgdmyp9m/k6+mW5jmsbtqb3Y4mMPWvOJFfz/sIvWJP0F0Zg== -"@spectrum-css/dialog@^3.0.1": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@spectrum-css/dialog/-/dialog-3.0.2.tgz#ae4db17c9772d822c538474f66bf838cea02217f" - integrity sha512-9aToA1fO5kE3ednoTKGKmaS8+X6noFJ4PvwZD1FgAZIA842PgHNW6e6hPw+MngZvqKqNcZsiuiJeRCq7TRkd4g== +"@spectrum-css/dialog@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/dialog/-/dialog-3.0.1.tgz#33aae036282159f6aa998848b8c0828640a9620a" + integrity sha512-hUFbRR6RGT63MNuP7wP+k9KU+uRuICsduMihskh700e+jiQ+Gsv53fBFDlB843FoZYlIXzFQXgtjMUC5a4Qibw== -"@spectrum-css/divider@^1.0.3": +"@spectrum-css/divider@1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@spectrum-css/divider/-/divider-1.0.3.tgz#639e2ebaa0834efa40f42397668bbd5c153ea385" integrity sha512-Zy4Rn40w8UtzMh3wx/U9+CepSCpm1aOCGftHgWDub0XZuVTzh0c1WwyzTuLCx2Hf21z5VRGNiDh8bGEEzSbtNA== dependencies: "@spectrum-css/vars" "^3.0.2" -"@spectrum-css/dropzone@^3.0.2": +"@spectrum-css/dropzone@3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@spectrum-css/dropzone/-/dropzone-3.0.2.tgz#34f137851054442b219fed7f32006b93fc5e0bcf" integrity sha512-BuBBzm5re6lM0AWgd6V+mI5eEGnnmFEtcFiJBEn9jYNEQYgflFhvnERUt89jMX5WmspiecwI2JBWJFrtFsOzug== -"@spectrum-css/fieldgroup@^3.0.2": +"@spectrum-css/fieldgroup@3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@spectrum-css/fieldgroup/-/fieldgroup-3.0.2.tgz#1c1afd3c444d8650fefac275dc66a7a913933846" integrity sha512-Vyw0kQJdLW18J6w4H+YAsoLntvkw5rXmW3CH5H3SDTXkBztxtHSSe3e106Nw5MoZxTfHlom6CxbYXYCTjQfqGw== -"@spectrum-css/fieldlabel@^3.0.1": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@spectrum-css/fieldlabel/-/fieldlabel-3.0.2.tgz#9c6d28e2c43f019cafa2bb75b54c8bc540669912" - integrity sha512-mQ1h3Uc6JXVa1nIgQK501GOgvlqqc/2VNSCJrE73I3MPeIt9kRv1ISAsDqUZnuot88BCWnnXfCkU4lSXY1RLog== +"@spectrum-css/fieldlabel@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/fieldlabel/-/fieldlabel-3.0.1.tgz#39f7c0f25cc2ff402afeff005341b0832f7c588c" + integrity sha512-LMfwrwIq8wEEvxFLobdLvXRwKrp8o9Fty4iJ9aYl2Rj1uXkfRd8qLz9HGZjLEE1OuJgoTBgamYABl7EvoA5PLw== -"@spectrum-css/icon@^3.0.1": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@spectrum-css/icon/-/icon-3.0.2.tgz#327dcb95ab86368a00eb5a6d898c2c02e4ae04b3" - integrity sha512-BdHoO2ttrbsj1+IfHmSCGNS0oEf8i2UW3agfOVtSlYOD+iGykupWwy8ANLB6p4GvjlR7YjPRGzDRGgmDwVqR0Q== +"@spectrum-css/icon@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/icon/-/icon-3.0.1.tgz#e300a6fc353c85c6b5d6e7a364408a940c31b177" + integrity sha512-cGFtIrcQ/7tthdkHK1npuEFiCdYVHLqwmLxghUYQw8Tb8KgJaw3OBO1tpjgsUizexNgu26BjVRIbGxNWuBXIHQ== -"@spectrum-css/illustratedmessage@^3.0.2": +"@spectrum-css/illustratedmessage@3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@spectrum-css/illustratedmessage/-/illustratedmessage-3.0.2.tgz#6a480be98b027e050b086e7899e40d87adb0a8c0" integrity sha512-dqnE8X27bGcO0HN8+dYx8O4o0dNNIAqeivOzDHhe2El+V4dTzMrNIerF6G0NLm3GjVf6XliwmitsZK+K6FmbtA== -"@spectrum-css/inlinealert@^2.0.1": +"@spectrum-css/inlinealert@2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@spectrum-css/inlinealert/-/inlinealert-2.0.1.tgz#7521f88f6c845806403cc7d925773c7414e204a2" integrity sha512-Xy5RCOwgurqUXuGQCsEDUduDd5408bmEpmFg+feynG7VFUgLFZWBeylSENB/OqjlFtO76PHXNVdHkhDscPIHTA== -"@spectrum-css/inputgroup@^3.0.2": +"@spectrum-css/inputgroup@3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@spectrum-css/inputgroup/-/inputgroup-3.0.2.tgz#f1b13603832cbd22394f3d898af13203961f8691" integrity sha512-O0G3Lw9gxsh8gTLQWIAKkN1O8cWhjpEUl+oR1PguIKFni72uNr2ikU9piOwy/r0gJG2Q/TVs6hAshoAAkmsSzw== -"@spectrum-css/label@^2.0.10": +"@spectrum-css/label@2.0.10": version "2.0.10" resolved "https://registry.yarnpkg.com/@spectrum-css/label/-/label-2.0.10.tgz#2368651d7636a19385b5d300cdf6272db1916001" integrity sha512-xCbtEiQkZIlLdWFikuw7ifDCC21DOC/KMgVrrVJHXFc4KRQe9LTZSqmGF3tovm+CSq1adE59mYoTbojVQ9YuEQ== -"@spectrum-css/link@^3.1.1": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@spectrum-css/link/-/link-3.1.2.tgz#b4af145b809ae74b3776ea14ea30b84b329a67ba" - integrity sha512-dODJ3fqsZtds2RNcozqUPR/aIqgjaWnFOpUxd8olwyDTMoFJjZ9Ts1WAHSEBM2NwUIr2nm9Hu5AgWH+6cmuowg== +"@spectrum-css/link@3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/link/-/link-3.1.1.tgz#cb526a2e10b50ef5a7ae29cca7272e2610d597eb" + integrity sha512-Bi88lRhTY7g6nM/ryW1yY4Cji211ZYNtRxkxbV7n2lPvwMAAQtyx0qVD3ru4kTGj/FFVvmPR3XiOE10K13HSNA== -"@spectrum-css/menu@^3.0.1": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@spectrum-css/menu/-/menu-3.0.2.tgz#1d133a433f71aeb4722ef98be00a11371276821d" - integrity sha512-r3Fom1+SB06iv7jxVebiR+rFgqS4qzoPSXiPt41MhdLznQ0L+9FWBid4eX4dLmPfBNsYdWjnK8Py+7uYCLmkAA== +"@spectrum-css/menu@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/menu/-/menu-3.0.1.tgz#2a376f991acc24e12ec892bb6b9db2650fc41fbe" + integrity sha512-Qjg0+1O0eC89sb/bRFq2AGnQ8XqhVy23TUXHyffNM8qdcMssnlny3QmhzjURCZKvx/Y5UytCpzhedPQqSpQwZg== -"@spectrum-css/modal@^3.0.1": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@spectrum-css/modal/-/modal-3.0.2.tgz#58b6621cab65f90788d310374f40df1f7090473f" - integrity sha512-YnIivJhoaao7Otu+HV7sgebPyFbO6sd/oMvTN/Rb2wwgnaMnIIuIRdGandSrcgotN2uNgs+P0knG6mv/xA1/dg== +"@spectrum-css/modal@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/modal/-/modal-3.0.1.tgz#613a6b83d0330a4d38db41a98090800751c56d8d" + integrity sha512-F7D99F3cjDGT9DM9sogx/p49jrNYT7a1J6TUoqV73wUf+0gP+dTsskBOo9jB8VbUE+POQPjiDLB+SWLp6iBB+w== -"@spectrum-css/pagination@^3.0.3": +"@spectrum-css/pagination@3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@spectrum-css/pagination/-/pagination-3.0.3.tgz#b204c3ada384c4af751a354bc428346d82eeea65" integrity sha512-OJ/v9GeNXJOZ9Yr9LDBYPrR2NCiLOWP9wANT/a5sqFuugRnQbn/HYMnRp9TBxwpDY6ihaPo0T/wi7kLiAJFdDw== -"@spectrum-css/picker@^1.0.1": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@spectrum-css/picker/-/picker-1.0.2.tgz#b49429ae3c89f9c5f2c0530787ce45392c9612ff" - integrity sha512-wfOtS6CdSba5GkN/i8WeCHpfROq2jW4MNWmRWc4tjfaBm23ASJYQhPsKINqWLE02Cr8E65kVoXy2EBpPY+w7pg== +"@spectrum-css/picker@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/picker/-/picker-1.0.1.tgz#98991198576d26bd14160824e7b6f3c278ff930b" + integrity sha512-Rv4/UBOdNW1gs7WVBCJnPD5VFly8MqP++psDX6kcugUIcfJy0GC3acvElotmKRlCDk8Qxks2W2A0jKeSgphTmA== -"@spectrum-css/popover@^3.0.1": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@spectrum-css/popover/-/popover-3.0.2.tgz#b031b07ed4b927aa67d082a71ebed08bd7ab28b3" - integrity sha512-5YiZh4iD8Abw02o0bZNccU59nEYzAsP5c8GO1/J5c7/PwxUzcrAlmzGNPXdsqyz5sm7QMeFZcgp4lr44TEnV5A== +"@spectrum-css/popover@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/popover/-/popover-3.0.1.tgz#5863c1efc53f98f9aba2de9186666780041303fc" + integrity sha512-LmOSj/yCwQQ9iGmCYnHiJsJR/HfPiGqI1Jl7pkKxBOCxYBMS/5+ans9vfCN2Qnd0eK7WSbfPg72S6mjye7db2Q== -"@spectrum-css/progressbar@^1.0.2": +"@spectrum-css/progressbar@1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@spectrum-css/progressbar/-/progressbar-1.0.2.tgz#b5a59432517f9ae6dad49d9504691bc5ac42b424" integrity sha512-+jExeBLtVCqo3BqtFq5WCtZ028Dzk+oUnX6y4z6ZamKPqOyOELOtFnhYnyhyRndQOqYwKUTXx9zsaWA/lpJOHw== -"@spectrum-css/progresscircle@^1.0.2": +"@spectrum-css/progresscircle@1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@spectrum-css/progresscircle/-/progresscircle-1.0.2.tgz#258ea9170fb70f795edda03e38a61d93bef4487c" integrity sha512-JLULpyzjIY95lzlWR1yE1gv4l1K6p+scQ+edmuZZUHBzwM3pUtkvHJmUlA9TYdResUYW6Uka60VRdY6lZ8gnFQ== -"@spectrum-css/radio@^3.0.2": +"@spectrum-css/radio@3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@spectrum-css/radio/-/radio-3.0.2.tgz#9c1386894920bbed604e4e174fbbd45d9d762152" integrity sha512-0TDdzC9omNXnpKHEXNuuGeXdNh4x8jvTKVUqMRLb7vY4hY94hAdt6X01NBqka+jzK35HxGzpDdPADAz62yZLPQ== -"@spectrum-css/search@^3.0.2": +"@spectrum-css/search@3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@spectrum-css/search/-/search-3.0.2.tgz#70e93e321032d40b399498b2324e3b70e050551e" integrity sha512-3UbT8yZmNOwrZxq+CUmumE+26ZySZ8OoKNM6U20SLMPLgdx6MrRugVE88r3Bl0sJ0RZX/5bU8nausdiHeX+Jlw== -"@spectrum-css/sidenav@^3.0.2": +"@spectrum-css/sidenav@3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@spectrum-css/sidenav/-/sidenav-3.0.2.tgz#9d70f408d588ee79c69857751010333671f32713" integrity sha512-YpIdH/F0jEICYmoduGrnkTmxwJq1kfKxEp0wOs+ZkQOsvKMv1an7nyhsfOKCQqcGNfYzJ9mJAk7/u5+vsxHa8g== @@ -251,67 +256,72 @@ resolved "https://registry.yarnpkg.com/@spectrum-css/slider/-/slider-3.0.1.tgz#5281e6f47eb5a4fd3d1816c138bf66d01d7f2e49" integrity sha512-DI2dtMRnQuDM1miVzl3SGyR1khUEKnwdXfO5EHDFwkC3yav43F5QogkfjmjFmWWobMVovdJlAuiaaJ/IHejD0Q== -"@spectrum-css/statuslight@^3.0.2": +"@spectrum-css/statuslight@3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@spectrum-css/statuslight/-/statuslight-3.0.2.tgz#dc54b6cd113413dcdb909c486b5d7bae60db65c5" integrity sha512-xodB8g8vGJH20XmUj9ZsPlM1jHrGeRbvmVXkz0q7YvQrYAhim8pP3W+XKKZAletPFAuu8cmUOc6SWn6i4X4z6w== -"@spectrum-css/stepper@^3.0.3": +"@spectrum-css/stepper@3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@spectrum-css/stepper/-/stepper-3.0.3.tgz#ae89846886431e3edeee060207b8f81540f73a34" integrity sha512-prAD61ImlOTs9b6PfB3cB08x4lAfxtvnW+RZiTYky0E8GgZdrc/MfCkL5/oqQaIQUtyQv/3Lb7ELAf/0K8QTXw== -"@spectrum-css/switch@^1.0.2": +"@spectrum-css/switch@1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@spectrum-css/switch/-/switch-1.0.2.tgz#f0b4c69271964573e02b08e90998096e49e1de44" integrity sha512-zqmHpgWPNg1gEwdUNFYV3CBX5JaeALfIqcJIxE0FLZqr9d1C4+oLE0ItIFzt1bwr4bFAOmkEpvtiY+amluzGxQ== -"@spectrum-css/table@^3.0.1": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@spectrum-css/table/-/table-3.0.2.tgz#c666743d569fef81ddc8810fac8cda53b315f8d7" - integrity sha512-nt/QNC7NmUank0wozd4FySEX1UIYXuvuOKDyN1II3sxfwFSpJfp/Df9KVMhrYs4EsmB4XMGcoxp8ND/CrvH3ow== +"@spectrum-css/table@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/table/-/table-3.0.1.tgz#753e0e2498082c0c36b9600828516aff3ac338cd" + integrity sha512-XQ+srMTv9hK1H0nctWUtqyzitmvyb5TNR+7mjAmKRdkBRSTQQSipDhenxZp72ekzMtMoSYZVZ77kgo0Iw3Fpug== -"@spectrum-css/tabs@^3.2.12": +"@spectrum-css/tabs@3.2.12": version "3.2.12" resolved "https://registry.yarnpkg.com/@spectrum-css/tabs/-/tabs-3.2.12.tgz#9b08f23d5aa881b3441af7757800c7173e5685ff" integrity sha512-rPFUW9SSW4+3/UJ3UrtY2/l3sQvlqB1fqxHLPDjgykvbfrnMejcCTNV4ZrFNHXpE/6+kGnk+yVViSPtWGwJzkA== -"@spectrum-css/tags@^3.0.2": +"@spectrum-css/tags@3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@spectrum-css/tags/-/tags-3.0.2.tgz#5bf35fb79c97cd9344de485bd4626ad5b9f07757" integrity sha512-HbvMk+QHvCDD1/ScvSErpKROcpAbXuMD4Hl/Gz/1A1lQ0fJ/CJeCq/MMsL7zjK1nlItU/ySu8r8KIuRF+6F8SQ== -"@spectrum-css/textfield@^3.0.1": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@spectrum-css/textfield/-/textfield-3.0.2.tgz#907f62d2dc82852dd6236a820be99e252b531631" - integrity sha512-nkFgAb0cP4jUodkUBErMNfyF78jJLtgL1Mrr9/rvGpGobo10IAbb8zZY4CkZ64o8XmMy/85+wZTKcx+KHatqpg== +"@spectrum-css/textfield@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/textfield/-/textfield-3.0.1.tgz#e875b8e37817378ad08fc4af7d53026df38911e5" + integrity sha512-MUV5q87CVxbkNdSNoxGrFbgyKc51ft/WWf3aVEoPdPw5yBnXqFe1w1YmAit5zYDOOhhs58sCLAlUcCMlOpkgrA== -"@spectrum-css/toast@^3.0.1": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@spectrum-css/toast/-/toast-3.0.2.tgz#8e27cce799b1b1d0054a88b135dddf7fbf1bdc78" - integrity sha512-sJp8DRsU2iSF67hlOQHFdRkbJHncspoOywHEsqcjh2KFl8gRY12rQL0ORG6J2THUt0LVBWSy48iwph9s4rkwsA== +"@spectrum-css/toast@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/toast/-/toast-3.0.1.tgz#36f62ea05302761e59b9d53e05f6c04423861796" + integrity sha512-jov++S358BrN2tmMfaoYk1N6u9HojgeuQk61keXrK2m3VE5/n94x7Lg3kIPeSWO0odyDfBlMqT9jacbRey3QTg== -"@spectrum-css/tooltip@^3.0.3": +"@spectrum-css/tooltip@3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@spectrum-css/tooltip/-/tooltip-3.0.3.tgz#26b8ca3b3d30e29630244d85eb4fc11d0c841281" integrity sha512-ztRF7WW1FzyNavXBRc+80z67UoOrY9wl3cMYsVD3MpDnyxdzP8cjza1pCcolKBaFqRTcQKkxKw3GWtGICRKR5A== -"@spectrum-css/treeview@^3.0.2": +"@spectrum-css/treeview@3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@spectrum-css/treeview/-/treeview-3.0.2.tgz#d54d8f17290babb1c885f5d9355e225421beb0d2" integrity sha512-foO7UBJv1JMFaKgDPVt8jBghZSVbqhXR8TaGaxHSnMubv7ygmKkc1AITrWC2STILCn84ju2vchOohMZfW6sYwg== -"@spectrum-css/typography@^3.0.1": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@spectrum-css/typography/-/typography-3.0.2.tgz#ea3ca0a60e18064527819d48c8c4364cab4fcd38" - integrity sha512-5ZOLmQe0edzsDMyhghUd4hBb5uxGsFrxzf+WasfcUw9klSfTsRZ09n1BsaaWbgrLjlMQ+EEHS46v5VNo0Ms2CA== +"@spectrum-css/typography@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/typography/-/typography-3.0.1.tgz#957dafd9b18c314fa37a88b549042ba2175f5b3f" + integrity sha512-XyR68K2rIZX3u4j7HhMLOqLVHDJZcapp3XUqgYMzMWccBFleA0qPxKpfRWqVIA5DzTMSIw0wEcZPYKWFZ2e6dA== -"@spectrum-css/underlay@^2.0.9": - version "2.0.10" - resolved "https://registry.yarnpkg.com/@spectrum-css/underlay/-/underlay-2.0.10.tgz#8b75b646605a311850f6620caa18d4996cd64ed7" - integrity sha512-PmsmkzeGD/rY4pp3ILXHt9w8BW7uaEqXe08hQRS7rGki7wqCpG4mE0/8N3yEcA3QxWQclmG9gdkg5uz6wMmYzA== +"@spectrum-css/underlay@2.0.9": + version "2.0.9" + resolved "https://registry.yarnpkg.com/@spectrum-css/underlay/-/underlay-2.0.9.tgz#fc10f971d1325cc844b727e6260f7217844060e8" + integrity sha512-X86xd0PG4QobmUyXA90BFGnyygaI8kW64dA4ysf4z0cOvUWjNbAAl3a/DB/WRyrnp63Zqv83T/cgNbetagTbWg== -"@spectrum-css/vars@^3.0.1", "@spectrum-css/vars@^3.0.2": +"@spectrum-css/vars@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/vars/-/vars-3.0.1.tgz#561fd69098f896a647242dd8d6108af603bfa31e" + integrity sha512-l4oRcCOqInChYXZN6OQhpe3isk6l4OE6Ys8cgdlsiKp53suNoQxyyd9p/eGRbCjZgH3xQ8nK0t4DHa7QYC0S6w== + +"@spectrum-css/vars@^3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@spectrum-css/vars/-/vars-3.0.2.tgz#ea9062c3c98dfc6ba59e5df14a03025ad8969999" integrity sha512-vzS9KqYXot4J3AEER/u618MXWAS+IoMvYMNrOoscKiLLKYQWenaueakUWulFonToPd/9vIpqtdbwxznqrK5qDw== @@ -367,13 +377,13 @@ dependencies: "@types/estree" "*" -accepts@~1.3.7: - version "1.3.7" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" - integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== +accepts@~1.3.8: + 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.24" - negotiator "0.6.2" + mime-types "~2.1.34" + negotiator "0.6.3" acorn@^7.3.1: version "7.4.1" @@ -422,7 +432,7 @@ argparse@^1.0.7: array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== async-limiter@~1.0.0: version "1.0.1" @@ -444,21 +454,23 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -body-parser@1.19.0: - version "1.19.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" - integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== +body-parser@1.20.1: + version "1.20.1" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" + integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== dependencies: - bytes "3.1.0" + bytes "3.1.2" content-type "~1.0.4" debug "2.6.9" - depd "~1.1.2" - http-errors "1.7.2" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" iconv-lite "0.4.24" - on-finished "~2.3.0" - qs "6.7.0" - raw-body "2.4.0" - type-is "~1.6.17" + on-finished "2.4.1" + qs "6.11.0" + raw-body "2.5.1" + type-is "~1.6.18" + unpipe "1.0.0" boolbase@^1.0.0, boolbase@~1.0.0: version "1.0.0" @@ -506,6 +518,11 @@ bytes@3.1.0: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -664,7 +681,7 @@ commondir@^1.0.1: 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== concat-with-sourcemaps@^1.1.0: version "1.1.0" @@ -673,12 +690,12 @@ concat-with-sourcemaps@^1.1.0: dependencies: source-map "^0.6.1" -content-disposition@0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" - integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== +content-disposition@0.5.4: + 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.1.2" + safe-buffer "5.2.1" content-type@~1.0.4: version "1.0.4" @@ -695,12 +712,12 @@ convert-source-map@^1.5.1: cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== -cookie@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" - integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== +cookie@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" + integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== cosmiconfig@^5.0.0: version "5.2.1" @@ -899,15 +916,20 @@ define-properties@^1.1.3: dependencies: object-keys "^1.0.12" +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== + depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= -destroy@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" - integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== dom-serializer@0: version "0.2.2" @@ -956,7 +978,7 @@ easymde@^2.16.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 sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== electron-to-chromium@^1.3.723: version "1.3.749" @@ -971,7 +993,7 @@ emojis-list@^3.0.0: encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== entities@^2.0.0: version "2.2.0" @@ -1029,7 +1051,7 @@ escalade@^3.1.1: escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== escape-string-regexp@^1.0.5: version "1.0.5" @@ -1059,7 +1081,7 @@ estree-walker@^2.0.1: etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== eventemitter3@^4.0.4: version "4.0.7" @@ -1088,37 +1110,38 @@ express-ws@^4.0.0: ws "^5.2.0" express@^4.16.3: - version "4.17.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" - integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== + version "4.18.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" + integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== dependencies: - accepts "~1.3.7" + accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.19.0" - content-disposition "0.5.3" + body-parser "1.20.1" + content-disposition "0.5.4" content-type "~1.0.4" - cookie "0.4.0" + cookie "0.5.0" cookie-signature "1.0.6" debug "2.6.9" - depd "~1.1.2" + depd "2.0.0" encodeurl "~1.0.2" escape-html "~1.0.3" etag "~1.8.1" - finalhandler "~1.1.2" + finalhandler "1.2.0" fresh "0.5.2" + http-errors "2.0.0" merge-descriptors "1.0.1" methods "~1.1.2" - on-finished "~2.3.0" + on-finished "2.4.1" parseurl "~1.3.3" path-to-regexp "0.1.7" - proxy-addr "~2.0.5" - qs "6.7.0" + proxy-addr "~2.0.7" + qs "6.11.0" range-parser "~1.2.1" - safe-buffer "5.1.2" - send "0.17.1" - serve-static "1.14.1" - setprototypeof "1.1.1" - statuses "~1.5.0" + safe-buffer "5.2.1" + send "0.18.0" + serve-static "1.15.0" + setprototypeof "1.2.0" + statuses "2.0.1" type-is "~1.6.18" utils-merge "1.0.1" vary "~1.1.2" @@ -1130,17 +1153,17 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -finalhandler@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" - integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== dependencies: debug "2.6.9" encodeurl "~1.0.2" escape-html "~1.0.3" - on-finished "~2.3.0" + on-finished "2.4.1" parseurl "~1.3.3" - statuses "~1.5.0" + statuses "2.0.1" unpipe "~1.0.0" flatpickr@^4.5.2: @@ -1148,15 +1171,15 @@ flatpickr@^4.5.2: resolved "https://registry.yarnpkg.com/flatpickr/-/flatpickr-4.6.9.tgz#9a13383e8a6814bda5d232eae3fcdccb97dc1499" integrity sha512-F0azNNi8foVWKSF+8X+ZJzz8r9sE1G4hl06RyceIaLvyltKvDl6vqk9Lm/6AUUCi5HWaIjiUbk7UpeE/fOXOpw== -forwarded@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" - integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== fs.realpath@^1.0.0: version "1.0.0" @@ -1180,7 +1203,16 @@ generic-names@^2.0.1: dependencies: loader-utils "^1.1.0" -get-intrinsic@^1.0.2, get-intrinsic@^1.1.1: +get-intrinsic@^1.0.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" + integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.3" + +get-intrinsic@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== @@ -1228,6 +1260,11 @@ has-symbols@^1.0.1, has-symbols@^1.0.2: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + has@^1.0.0, has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -1250,18 +1287,7 @@ hsla-regex@^1.0.0: resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg= -http-errors@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" - integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - -http-errors@1.7.3, http-errors@~1.7.2: +http-errors@1.7.3: version "1.7.3" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== @@ -1272,6 +1298,17 @@ http-errors@1.7.3, http-errors@~1.7.2: statuses ">= 1.5.0 < 2" toidentifier "1.0.0" +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -1337,11 +1374,6 @@ inherits@2, inherits@2.0.4: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" @@ -1522,9 +1554,9 @@ json-parse-even-better-errors@^2.3.0: integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== json5@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" - integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + version "1.0.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== dependencies: minimist "^1.2.0" @@ -1533,10 +1565,10 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= -loader-utils@^1.1.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" - integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== +loader-utils@1.4.1, loader-utils@^1.1.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.1.tgz#278ad7006660bccc4d2c0c1578e17c5c78d5c0e0" + integrity sha512-1Qo97Y2oKaU+Ro2xnDMR26g1BwMT29jNbem1EvcujW2jqt+j5COXyscjM7bLQkM9HaxI7pkWeW7gnI072yMI9Q== dependencies: big.js "^5.2.2" emojis-list "^3.0.0" @@ -1582,12 +1614,12 @@ mdn-data@2.0.4: media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== merge-stream@^2.0.0: version "2.0.0" @@ -1597,36 +1629,48 @@ merge-stream@^2.0.0: methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== mime-db@1.47.0: version "1.47.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.47.0.tgz#8cb313e59965d3c05cfbf898915a267af46a335c" integrity sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw== -mime-types@^2.1.24, mime-types@~2.1.24: +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.24: version "2.1.30" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.30.tgz#6e7be8b4c479825f85ed6326695db73f9305d62d" integrity sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg== dependencies: mime-db "1.47.0" +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: + mime-db "1.52.0" + mime@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== 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== + 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.2.0, minimist@^1.2.5: - version "1.2.6" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" - integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== + version "1.2.7" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" + integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== mkdirp@~0.5.1: version "0.5.5" @@ -1638,14 +1682,9 @@ mkdirp@~0.5.1: 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.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" - integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== - -ms@^2.1.1: +ms@2.1.3, ms@^2.1.1: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -1655,10 +1694,10 @@ nanoid@^3.1.22: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.0.tgz#5906f776fd886c66c24f3653e0c46fcb1d4ad6b0" integrity sha512-JzxqqT5u/x+/KOFSd7JP15DOo9nOoHpx6DYatqIHUW2+flybkm+mdcraotSQR5WcnZr+qhGVh8Ted0KdfSMxlg== -negotiator@0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" - integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== +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-releases@^1.1.71: version "1.1.73" @@ -1701,9 +1740,9 @@ nth-check@^1.0.2: boolbase "~1.0.0" object-inspect@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a" - integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw== + version "1.12.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" + integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" @@ -1739,10 +1778,10 @@ object.values@^1.1.0: es-abstract "^1.18.0-next.2" has "^1.0.3" -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= +on-finished@2.4.1: + 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: ee-first "1.1.1" @@ -1821,7 +1860,7 @@ path-parse@^1.0.6: path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== path-type@^4.0.0: version "4.0.0" @@ -2186,12 +2225,12 @@ promise.series@^0.2.0: resolved "https://registry.yarnpkg.com/promise.series/-/promise.series-0.2.0.tgz#2cc7ebe959fc3a6619c04ab4dbdc9e452d864bbd" integrity sha1-LMfr6Vn8OmYZwEq029yeRS2GS70= -proxy-addr@~2.0.5: - version "2.0.6" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" - integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw== +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== dependencies: - forwarded "~0.1.2" + forwarded "0.2.0" ipaddr.js "1.9.1" q@^1.1.2: @@ -2199,10 +2238,12 @@ q@^1.1.2: resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= -qs@6.7.0: - version "6.7.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" - integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== +qs@6.11.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" randombytes@^2.1.0: version "2.1.0" @@ -2216,13 +2257,13 @@ range-parser@~1.2.1: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" - integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== +raw-body@2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" + integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== dependencies: - bytes "3.1.0" - http-errors "1.7.2" + bytes "3.1.2" + http-errors "2.0.0" iconv-lite "0.4.24" unpipe "1.0.0" @@ -2332,16 +2373,16 @@ rollup@^2.45.2: optionalDependencies: fsevents "~2.3.1" -safe-buffer@5.1.2, 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-buffer@^5.1.0: +safe-buffer@5.2.1, safe-buffer@^5.1.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: + 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-identifier@^0.4.2: version "0.4.2" resolved "https://registry.yarnpkg.com/safe-identifier/-/safe-identifier-0.4.2.tgz#cf6bfca31c2897c588092d1750d30ef501d59fcb" @@ -2357,24 +2398,24 @@ sax@~1.2.4: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -send@0.17.1: - version "0.17.1" - resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" - integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== +send@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== dependencies: debug "2.6.9" - depd "~1.1.2" - destroy "~1.0.4" + depd "2.0.0" + destroy "1.2.0" encodeurl "~1.0.2" escape-html "~1.0.3" etag "~1.8.1" fresh "0.5.2" - http-errors "~1.7.2" + http-errors "2.0.0" mime "1.6.0" - ms "2.1.1" - on-finished "~2.3.0" + ms "2.1.3" + on-finished "2.4.1" range-parser "~1.2.1" - statuses "~1.5.0" + statuses "2.0.1" serialize-javascript@^4.0.0: version "4.0.0" @@ -2383,21 +2424,26 @@ serialize-javascript@^4.0.0: dependencies: randombytes "^2.1.0" -serve-static@1.14.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" - integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== +serve-static@1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== dependencies: encodeurl "~1.0.2" escape-html "~1.0.3" parseurl "~1.3.3" - send "0.17.1" + send "0.18.0" setprototypeof@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== +setprototypeof@1.2.0: + version "1.2.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" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -2410,6 +2456,15 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" @@ -2455,7 +2510,12 @@ stable@^0.1.8: resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== -"statuses@>= 1.5.0 < 2", statuses@~1.5.0: +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +"statuses@>= 1.5.0 < 2": version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= @@ -2579,7 +2639,12 @@ toidentifier@1.0.0: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== -type-is@~1.6.17, type-is@~1.6.18: +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== + +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== @@ -2615,7 +2680,7 @@ uniqs@^2.0.0: unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== unquote@~1.1.1: version "1.1.1" @@ -2640,12 +2705,12 @@ util.promisify@~1.0.0: utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== vendors@^1.0.0: version "1.0.4" diff --git a/packages/builder/assets/backups-default.png b/packages/builder/assets/backups-default.png new file mode 100644 index 0000000000..6e37cbb6c7 Binary files /dev/null and b/packages/builder/assets/backups-default.png differ diff --git a/packages/builder/assets/bb-emblem.svg b/packages/builder/assets/bb-emblem.svg index 9f4f3690d5..7d499e4862 100644 --- a/packages/builder/assets/bb-emblem.svg +++ b/packages/builder/assets/bb-emblem.svg @@ -3,7 +3,7 @@ diff --git a/packages/builder/cypress.json b/packages/builder/cypress.json deleted file mode 100644 index f1eada481f..0000000000 --- a/packages/builder/cypress.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "baseUrl": "http://localhost:4100", - "video": true, - "projectId": "bmbemn", - "reporter": "cypress-multi-reporters", - "reporterOptions": { - "configFile": "reporterConfig.json" - }, - "env": { - "PORT": "4100", - "WORKER_PORT": "4200", - "JWT_SECRET": "test", - "HOST_IP": "" - }, - "retries": { - "runMode": 1, - "openMode": 0 - } -} \ No newline at end of file diff --git a/packages/builder/cypress/fixtures/apikey.json b/packages/builder/cypress/fixtures/apikey.json deleted file mode 100644 index 1def14ed6c..0000000000 --- a/packages/builder/cypress/fixtures/apikey.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "budibase": "CB373643-3FC4-4902-9E31-449C0ED066B6" -} \ No newline at end of file diff --git a/packages/builder/cypress/fixtures/example.json b/packages/builder/cypress/fixtures/example.json deleted file mode 100644 index da18d9352a..0000000000 --- a/packages/builder/cypress/fixtures/example.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "Using fixtures to represent data", - "email": "hello@cypress.io", - "body": "Fixtures are a great way to mock data for responses to routes" -} \ No newline at end of file diff --git a/packages/builder/cypress/fixtures/exported-app.txt b/packages/builder/cypress/fixtures/exported-app.txt deleted file mode 100644 index 28aeaf958a..0000000000 --- a/packages/builder/cypress/fixtures/exported-app.txt +++ /dev/null @@ -1,3 +0,0 @@ -{"version":"1.2.9","db_type":"http","start_time":"2022-05-12T13:47:50.453Z","db_info":{"db_name":"app_dev_7cae7fa4db8745da848f91430a8211bf","purge_seq":"0-g1AAAABXeJzLYWBgYMpgTmEQTM4vTc5ISXIwNDLXMwBCwxyQVB4LkGRoAFL_gSArkQGP2kSGpHqIoiwAtOgYRA","update_seq":"11-g1AAAACbeJzLYWBgYMpgTmEQTM4vTc5ISXIwNDLXMwBCwxyQVB4LkGRoAFL_gSArgzmRPRcowJ5klmZgaZmKTR8e0xIZkuqhxrBAjEk0NzcwMsSmIQsA89QoWg","sizes":{"file":94626,"external":5665,"active":7627},"props":{},"doc_del_count":0,"doc_count":7,"disk_format_version":8,"compact_running":false,"cluster":{"q":2,"n":1,"w":1,"r":1},"instance_start_time":"0","host":"http://localhost:4005/app_dev_7cae7fa4db8745da848f91430a8211bf/","auto_compaction":false,"adapter":"http"}} -{"docs":[{"_id":"_design/database","_rev":"4-ad6d41ef604ab34da380438c1be89521","views":{"by_link":{"map":"function (doc) {\n // everything in this must remain constant as its going to Pouch, no external variables\n if (doc.type === \"link\") {\n let doc1 = doc.doc1;\n let doc2 = doc.doc2;\n // eslint-disable-next-line no-undef\n emit([doc1.tableId, doc1.rowId], {\n id: doc2.rowId,\n thisId: doc1.rowId,\n fieldName: doc1.fieldName,\n });\n // if linking to same table can't emit twice\n if (doc1.tableId !== doc2.tableId) {\n // eslint-disable-next-line no-undef\n emit([doc2.tableId, doc2.rowId], {\n id: doc1.rowId,\n thisId: doc2.rowId,\n fieldName: doc2.fieldName,\n });\n }\n }\n }"},"screen_routes":{"map":"function(doc) {\n if (doc._id.startsWith(\"screen_\")) {\n emit(doc._id, {\n id: doc._id,\n routing: doc.routing,\n })\n }\n }"}},"indexes":{"rows":{"index":"function (doc) {\n function idx(input, prev) {\n for (let key of Object.keys(input)) {\n let idxKey = prev != null ? `${prev}.${key}` : key;\n idxKey = idxKey.replace(/ /g, \"_\");\n if (Array.isArray(input[key])) {\n for (let val of input[key]) {\n if (typeof val !== \"object\") {\n // eslint-disable-next-line no-undef\n index(idxKey, val, { store: true });\n }\n }\n }\n else if (key === \"_id\" || key === \"_rev\" || input[key] == null) {\n continue;\n }\n if (typeof input[key] === \"string\") {\n // eslint-disable-next-line no-undef\n index(idxKey, input[key].toLowerCase(), { store: true });\n }\n else if (typeof input[key] !== \"object\") {\n // eslint-disable-next-line no-undef\n index(idxKey, input[key], { store: true });\n }\n else {\n idx(input[key], idxKey);\n }\n }\n }\n if (doc._id.startsWith(\"ro_\")) {\n // eslint-disable-next-line no-undef\n index(\"default\", doc._id);\n idx(doc);\n }\n }","analyzer":"keyword"}},"_revisions":{"start":4,"ids":["ad6d41ef604ab34da380438c1be89521","cb47f4fd824d5e096ac8b76117e6f93d","68a9a1d1b01b327676ecbaa4b1e5b8a7","0b24e44a44af45e51e562fd124ce3007"]}},{"_id":"app_metadata","_rev":"2-a4fe55378bfec0fc71e58a1364ac9965","appId":"app_dev_7cae7fa4db8745da848f91430a8211bf","type":"app","version":"1.0.155-alpha.0","componentLibraries":["@budibase/standard-components"],"name":"My app","url":"/my-app","instance":{"_id":"app_dev_7cae7fa4db8745da848f91430a8211bf"},"tenantId":"default","updatedAt":"2022-05-12T13:47:28.756Z","createdAt":"2022-05-12T13:47:26.825Z","status":"development","_revisions":{"start":2,"ids":["a4fe55378bfec0fc71e58a1364ac9965","f0d06bdb6e6ae4781eb5c4aa224002ff"]}},{"_id":"layout_private_master","_rev":"1-c02411b58d697e38763889a375d52159","componentLibraries":["@budibase/standard-components"],"title":"My app","favicon":"./_shared/favicon.png","stylesheets":[],"name":"Navigation Layout","props":{"_id":"4f569166-a4f3-47ea-a09e-6d218c75586f","_instanceName":"Navigation Layout","_component":"@budibase/standard-components/layout","_children":[{"_id":"7fcf11e4-6f5b-4085-8e0d-9f3d44c98967","_component":"@budibase/standard-components/screenslot","_instanceName":"Screen slot","_styles":{"normal":{"flex":"1 1 auto","display":"flex","flex-direction":"column","justify-content":"flex-start","align-items":"stretch"},"hover":{},"active":{},"selected":{}},"_children":[]}],"_styles":{"active":{},"hover":{},"normal":{},"selected":{}},"title":"My app","navigation":"Top","width":"Large","links":[{"text":"Home","url":"/"}]}},{"_id":"layout_public_master","_rev":"1-d6bce47046d4d0de4f19a6ff95064109","componentLibraries":["@budibase/standard-components"],"title":"My app","favicon":"./_shared/favicon.png","stylesheets":[],"name":"Empty Layout","props":{"_id":"3723ffa1-f9e0-4c05-8013-98195c788ed6","_instanceName":"Empty Layout","_component":"@budibase/standard-components/layout","_children":[{"_id":"7fcf11e4-6f5b-4085-8e0d-9f3d44c98967","_component":"@budibase/standard-components/screenslot","_instanceName":"Screen slot","_styles":{"normal":{"flex":"1 1 auto","display":"flex","flex-direction":"column","justify-content":"flex-start","align-items":"stretch"},"hover":{},"active":{},"selected":{}},"_children":[]}],"_styles":{"active":{},"hover":{},"normal":{},"selected":{}},"navigation":"None","width":"Large","links":[{"text":"Home","url":"/"}]}},{"_id":"screen_b19c454fa5ae41ab874a8860623ab37c","_rev":"1-02dddd460ad14de50dc885aa362452c6","layoutId":"layout_private_master","props":{"_id":"c87457efbc05a43f39064d9dacc9d4b67","_component":"@budibase/standard-components/container","_styles":{"normal":{},"hover":{},"active":{},"selected":{}},"_children":[],"_instanceName":"New Screen","direction":"column","hAlign":"stretch","vAlign":"top","size":"grow","gap":"M"},"routing":{"route":"/home","roleId":"BASIC","roldId":"BASIC"},"name":"screen-id"},{"_id":"ta_users","_rev":"1-30a4344f056c24cf776d5736eb3c7ed5","type":"table","views":{},"name":"Users","schema":{"email":{"type":"string","constraints":{"type":"string","email":true,"length":{"maximum":""},"presence":true},"fieldName":"email","name":"email"},"firstName":{"name":"firstName","fieldName":"firstName","type":"string","constraints":{"type":"string","presence":false}},"lastName":{"name":"lastName","fieldName":"lastName","type":"string","constraints":{"type":"string","presence":false}},"roleId":{"fieldName":"roleId","name":"roleId","type":"options","constraints":{"type":"string","presence":false,"inclusion":["ADMIN","POWER","BASIC","PUBLIC"]}},"status":{"fieldName":"status","name":"status","type":"options","constraints":{"type":"string","presence":false,"inclusion":["active","inactive"]}}},"primaryDisplay":"email"}]} -{"seq":"11-g1AAAACbeJzLYWBgYMpgTmEQTM4vTc5ISXIwNDLXMwBCwxyQVCJDUv3___-zMpgTWXKBAuxJiebmBkaG2DTgMSaPBUgyNACp_1DT2CGmmaUZWFqmYtOXBQAWQiha"} diff --git a/packages/builder/cypress/fixtures/profile.json b/packages/builder/cypress/fixtures/profile.json deleted file mode 100644 index b6c355ca5c..0000000000 --- a/packages/builder/cypress/fixtures/profile.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "id": 8739, - "name": "Jane", - "email": "jane@example.com" -} \ No newline at end of file diff --git a/packages/builder/cypress/fixtures/users.json b/packages/builder/cypress/fixtures/users.json deleted file mode 100644 index f82fc8dfb2..0000000000 --- a/packages/builder/cypress/fixtures/users.json +++ /dev/null @@ -1,232 +0,0 @@ -[ - { - "id": 1, - "name": "Leanne Graham", - "username": "Bret", - "email": "Sincere@april.biz", - "address": { - "street": "Kulas Light", - "suite": "Apt. 556", - "city": "Gwenborough", - "zipcode": "92998-3874", - "geo": { - "lat": "-37.3159", - "lng": "81.1496" - } - }, - "phone": "1-770-736-8031 x56442", - "website": "hildegard.org", - "company": { - "name": "Romaguera-Crona", - "catchPhrase": "Multi-layered client-server neural-net", - "bs": "harness real-time e-markets" - } - }, - { - "id": 2, - "name": "Ervin Howell", - "username": "Antonette", - "email": "Shanna@melissa.tv", - "address": { - "street": "Victor Plains", - "suite": "Suite 879", - "city": "Wisokyburgh", - "zipcode": "90566-7771", - "geo": { - "lat": "-43.9509", - "lng": "-34.4618" - } - }, - "phone": "010-692-6593 x09125", - "website": "anastasia.net", - "company": { - "name": "Deckow-Crist", - "catchPhrase": "Proactive didactic contingency", - "bs": "synergize scalable supply-chains" - } - }, - { - "id": 3, - "name": "Clementine Bauch", - "username": "Samantha", - "email": "Nathan@yesenia.net", - "address": { - "street": "Douglas Extension", - "suite": "Suite 847", - "city": "McKenziehaven", - "zipcode": "59590-4157", - "geo": { - "lat": "-68.6102", - "lng": "-47.0653" - } - }, - "phone": "1-463-123-4447", - "website": "ramiro.info", - "company": { - "name": "Romaguera-Jacobson", - "catchPhrase": "Face to face bifurcated interface", - "bs": "e-enable strategic applications" - } - }, - { - "id": 4, - "name": "Patricia Lebsack", - "username": "Karianne", - "email": "Julianne.OConner@kory.org", - "address": { - "street": "Hoeger Mall", - "suite": "Apt. 692", - "city": "South Elvis", - "zipcode": "53919-4257", - "geo": { - "lat": "29.4572", - "lng": "-164.2990" - } - }, - "phone": "493-170-9623 x156", - "website": "kale.biz", - "company": { - "name": "Robel-Corkery", - "catchPhrase": "Multi-tiered zero tolerance productivity", - "bs": "transition cutting-edge web services" - } - }, - { - "id": 5, - "name": "Chelsey Dietrich", - "username": "Kamren", - "email": "Lucio_Hettinger@annie.ca", - "address": { - "street": "Skiles Walks", - "suite": "Suite 351", - "city": "Roscoeview", - "zipcode": "33263", - "geo": { - "lat": "-31.8129", - "lng": "62.5342" - } - }, - "phone": "(254)954-1289", - "website": "demarco.info", - "company": { - "name": "Keebler LLC", - "catchPhrase": "User-centric fault-tolerant solution", - "bs": "revolutionize end-to-end systems" - } - }, - { - "id": 6, - "name": "Mrs. Dennis Schulist", - "username": "Leopoldo_Corkery", - "email": "Karley_Dach@jasper.info", - "address": { - "street": "Norberto Crossing", - "suite": "Apt. 950", - "city": "South Christy", - "zipcode": "23505-1337", - "geo": { - "lat": "-71.4197", - "lng": "71.7478" - } - }, - "phone": "1-477-935-8478 x6430", - "website": "ola.org", - "company": { - "name": "Considine-Lockman", - "catchPhrase": "Synchronised bottom-line interface", - "bs": "e-enable innovative applications" - } - }, - { - "id": 7, - "name": "Kurtis Weissnat", - "username": "Elwyn.Skiles", - "email": "Telly.Hoeger@billy.biz", - "address": { - "street": "Rex Trail", - "suite": "Suite 280", - "city": "Howemouth", - "zipcode": "58804-1099", - "geo": { - "lat": "24.8918", - "lng": "21.8984" - } - }, - "phone": "210.067.6132", - "website": "elvis.io", - "company": { - "name": "Johns Group", - "catchPhrase": "Configurable multimedia task-force", - "bs": "generate enterprise e-tailers" - } - }, - { - "id": 8, - "name": "Nicholas Runolfsdottir V", - "username": "Maxime_Nienow", - "email": "Sherwood@rosamond.me", - "address": { - "street": "Ellsworth Summit", - "suite": "Suite 729", - "city": "Aliyaview", - "zipcode": "45169", - "geo": { - "lat": "-14.3990", - "lng": "-120.7677" - } - }, - "phone": "586.493.6943 x140", - "website": "jacynthe.com", - "company": { - "name": "Abernathy Group", - "catchPhrase": "Implemented secondary concept", - "bs": "e-enable extensible e-tailers" - } - }, - { - "id": 9, - "name": "Glenna Reichert", - "username": "Delphine", - "email": "Chaim_McDermott@dana.io", - "address": { - "street": "Dayna Park", - "suite": "Suite 449", - "city": "Bartholomebury", - "zipcode": "76495-3109", - "geo": { - "lat": "24.6463", - "lng": "-168.8889" - } - }, - "phone": "(775)976-6794 x41206", - "website": "conrad.com", - "company": { - "name": "Yost and Sons", - "catchPhrase": "Switchable contextually-based project", - "bs": "aggregate real-time technologies" - } - }, - { - "id": 10, - "name": "Clementina DuBuque", - "username": "Moriah.Stanton", - "email": "Rey.Padberg@karina.biz", - "address": { - "street": "Kattie Turnpike", - "suite": "Suite 198", - "city": "Lebsackbury", - "zipcode": "31428-2261", - "geo": { - "lat": "-38.2386", - "lng": "57.2232" - } - }, - "phone": "024-648-3804", - "website": "ambrose.net", - "company": { - "name": "Hoeger LLC", - "catchPhrase": "Centralized empowering task-force", - "bs": "target end-to-end tables" - } - } -] \ No newline at end of file diff --git a/packages/builder/cypress/integration/addMultiOptionDatatype.spec.js b/packages/builder/cypress/integration/addMultiOptionDatatype.spec.js deleted file mode 100644 index f436f3ff39..0000000000 --- a/packages/builder/cypress/integration/addMultiOptionDatatype.spec.js +++ /dev/null @@ -1,45 +0,0 @@ -import filterTests from "../support/filterTests" -const interact = require('../support/interact') - -filterTests(['all'], () => { - context("Add Multi-Option Datatype", () => { - before(() => { - cy.login() - cy.createTestApp() - }) - - it("should create a new table, with data", () => { - cy.createTable("Multi Data") - cy.addColumn("Multi Data", "Test Data", "Multi-select", "1\n2\n3\n4\n5") - cy.addRowMultiValue(["1", "2", "3", "4", "5"]) - }) - - it("should add form with multi select picker, containing 5 options", () => { - cy.navigateToFrontend() - // Add data provider - cy.searchAndAddComponent("Data Provider") - cy.get(interact.DATASOURCE_PROP_CONTROL).click() - cy.get(interact.DROPDOWN).contains("Multi Data").click() - // Add Form with schema to match table - cy.searchAndAddComponent("Form") - cy.get(interact.DATASOURCE_PROP_CONTROL).click() - cy.get(interact.DROPDOWN).contains("Multi Data").click() - // Add multi-select picker to form - cy.searchAndAddComponent("Multi-select Picker").then(componentId => { - cy.get(interact.DATASOURCE_FIELD_CONTROL).type("Test Data").type("{enter}") - cy.wait(1000) - cy.getComponent(componentId).contains("Choose some options").click() - // Check picker has 5 items - cy.getComponent(componentId).find("li").should("have.length", 5) - // Select all items - for (let i = 1; i < 6; i++) { - cy.getComponent(componentId).find("li").contains(i).click() - } - // Check items have been selected - cy.getComponent(componentId) - .find(interact.SPECTRUM_PICKER_LABEL) - .contains("(5)") - }) - }) - }) -}) diff --git a/packages/builder/cypress/integration/addRadioButtons.spec.js b/packages/builder/cypress/integration/addRadioButtons.spec.js deleted file mode 100644 index 91cfd0c7fa..0000000000 --- a/packages/builder/cypress/integration/addRadioButtons.spec.js +++ /dev/null @@ -1,43 +0,0 @@ -import filterTests from "../support/filterTests" -const interact = require('../support/interact') - -filterTests(['all'], () => { - context("Add Radio Buttons", () => { - before(() => { - cy.login() - cy.createTestApp() - }) - - it("should add Radio Buttons options picker on form, add data, and confirm", () => { - cy.navigateToFrontend() - cy.searchAndAddComponent("Form") - cy.searchAndAddComponent("Options Picker").then((componentId) => { - // Provide field setting - cy.get(interact.DATASOURCE_FIELD_CONTROL).type("1") - // Open dropdown and select Radio buttons - cy.get(interact.OPTION_TYPE_PROP_CONTROL).click().then(() => { - cy.get(interact.SPECTRUM_POPOVER).contains('Radio buttons') - .click() - }) - const radioButtonsTotal = 3 - // Add values and confirm total - addRadioButtonData(radioButtonsTotal) - cy.getComponent(componentId).find('[type="radio"]') - .should('have.length', radioButtonsTotal) - }) - }) - - const addRadioButtonData = (totalRadioButtons) => { - cy.get(interact.OPTION_SOURCE_PROP_CONROL).click().then(() => { - cy.get(interact.SPECTRUM_POPOVER).contains('Custom') - .click() - .wait(1000) - }) - cy.addCustomSourceOptions(totalRadioButtons) - } - - after(() => { - cy.deleteAllApps() - }) - }) -}) diff --git a/packages/builder/cypress/integration/adminAndManagement/accountPortals.spec.js b/packages/builder/cypress/integration/adminAndManagement/accountPortals.spec.js deleted file mode 100644 index 448240f81d..0000000000 --- a/packages/builder/cypress/integration/adminAndManagement/accountPortals.spec.js +++ /dev/null @@ -1,116 +0,0 @@ -import filterTests from "../../support/filterTests" -const interact = require('../../support/interact') - -filterTests(["smoke", "all"], () => { - context("Account Portals", () => { - - const bbUserEmail = "bbuser@test.com" - - before(() => { - cy.login() - cy.deleteApp("Cypress Tests") - cy.createApp("Cypress Tests", false) - - // Create new user - cy.wait(500) - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000}) - cy.createUser(bbUserEmail) - cy.contains("bbuser").click() - cy.wait(500) - - // Reset password - cy.get(".title").within(() => { - cy.get(interact.SPECTRUM_ICON).click({ force: true }) - }) - cy.get(interact.SPECTRUM_MENU).within(() => { - cy.get(interact.SPECTRUM_MENU_ITEM).contains("Force password reset").click({ force: true }) - }) - - cy.get(interact.SPECTRUM_DIALOG_GRID) - .find(interact.SPECTRUM_TEXTFIELD_INPUT).invoke('val').as('pwd') - - cy.get(interact.SPECTRUM_BUTTON).contains("Reset password").click({ force: true }) - - // Login as new user and set password - cy.logOut() - cy.get('@pwd').then((pwd) => { - cy.login(bbUserEmail, pwd) - }) - - for (let i = 0; i < 2; i++) { - cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).eq(i).type("test") - } - cy.get(interact.SPECTRUM_BUTTON).contains("Reset your password").click({ force: true }) - //cy.logoutNoAppGrid() - }) - - it("should verify Standard Portal", () => { - // Development access should be disabled (Admin access is already disabled) - cy.login() - cy.setUserRole("bbuser", "App User") - bbUserLogin() - - // Verify Standard Portal - cy.get(interact.SPECTRUM_SIDENAV).should('not.exist') // No config sections - cy.get(interact.CREATE_APP_BUTTON).should('not.exist') // No create app button - cy.get(".app").should('not.exist') // No apps -> no roles assigned to user - cy.get(interact.CONTAINER).should('contain', bbUserEmail) // Message containing users email - - cy.logoutNoAppGrid() - }) - - it("should verify Admin Portal", () => { - cy.login() - // Configure user role - cy.setUserRole("bbuser", "Admin") - bbUserLogin() - - // Verify available options for Admin portal - cy.get(interact.SPECTRUM_SIDENAV) - .should('contain', 'Apps') - //.and('contain', 'Usage') - .and('contain', 'Users') - .and('contain', 'Auth') - .and('contain', 'Email') - .and('contain', 'Organisation') - .and('contain', 'Theming') - .and('contain', 'Update') - //.and('contain', 'Upgrade') - - cy.logOut() - }) - - it("should verify Development Portal", () => { - // Only Development access should be enabled - cy.login() - cy.setUserRole("bbuser", "Developer") - bbUserLogin() - - // Verify available options for Admin portal - cy.get(interact.SPECTRUM_SIDENAV) - .should('contain', 'Apps') - //.and('contain', 'Usage') - .and('not.contain', 'Users') - .and('not.contain', 'Auth') - .and('not.contain', 'Email') - .and('not.contain', 'Organisation') - .and('contain', 'Theming') - .and('not.contain', 'Update') - .and('not.contain', 'Upgrade') - - cy.logOut() - }) - - const bbUserLogin = () => { - // Login as bbuser - cy.logOut() - cy.login(bbUserEmail, "test") - } - - after(() => { - cy.login() - // Delete BB user - cy.deleteUser(bbUserEmail) - }) - }) -}) diff --git a/packages/builder/cypress/integration/adminAndManagement/authentication.spec.js b/packages/builder/cypress/integration/adminAndManagement/authentication.spec.js deleted file mode 100644 index 5cc42cb59a..0000000000 --- a/packages/builder/cypress/integration/adminAndManagement/authentication.spec.js +++ /dev/null @@ -1,178 +0,0 @@ -import filterTests from "../../support/filterTests" -// const interact = require("../support/interact") - -filterTests(["smoke", "all"], () => { - context("Auth Configuration", () => { - before(() => { - cy.login() - }) - - after(() => { - cy.get(".spectrum-SideNav li").contains("Auth").click() - cy.location().should(loc => { - expect(loc.pathname).to.eq("/builder/portal/manage/auth") - }) - - cy.get("[data-cy=new-scope-input]").clear() - - cy.get("div.content").scrollTo("bottom") - cy.get("[data-cy=oidc-active]").click() - - cy.get("[data-cy=oidc-active]").should('not.be.checked') - - cy.intercept("POST", "/api/global/configs").as("updateAuth") - cy.get("button[data-cy=oidc-save]").contains("Save").click({force: true}) - cy.wait("@updateAuth") - cy.get("@updateAuth").its("response.statusCode").should("eq", 200) - - cy.get(".spectrum-Toast-content") - .contains("Settings saved") - .should("be.visible") - }) - - it("Should allow updating of the OIDC config", () => { - cy.get(".spectrum-SideNav li").contains("Auth").click() - cy.location().should(loc => { - expect(loc.pathname).to.eq("/builder/portal/manage/auth") - }) - cy.get("div.content").scrollTo("bottom") - cy.get(".spectrum-Toast .spectrum-ClearButton").click() - - cy.get("input[data-cy=configUrl]").type("http://budi-auth.com/v2") - cy.get("input[data-cy=clientID]").type("34ac6a13-f24a-4b52-c70d-fa544ffd11b2") - cy.get("input[data-cy=clientSecret]").type("12A8Q~4nS_DWhOOJ2vWIRsNyDVsdtXPD.Zxa9df_") - - cy.get("button[data-cy=oidc-save]").should("not.be.disabled"); - - cy.intercept("POST", "/api/global/configs").as("updateAuth") - cy.get("button[data-cy=oidc-save]").contains("Save").click({force: true}) - cy.wait("@updateAuth") - cy.get("@updateAuth").its("response.statusCode").should("eq", 200) - - cy.get(".spectrum-Toast-content") - .contains("Settings saved") - .should("be.visible") - }) - - it("Should display default scopes in advanced config.", () => { - cy.get(".spectrum-SideNav li").contains("Auth").click() - cy.location().should(loc => { - expect(loc.pathname).to.eq("/builder/portal/manage/auth") - }) - cy.get("div.content").scrollTo("bottom") - - cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4) - - cy.get(".spectrum-Tags-item").contains("openid") - cy.get(".spectrum-Tags-item").contains("openid").find(".spectrum-ClearButton").should("not.exist") - - cy.get(".spectrum-Tags-item").contains("offline_access") - cy.get(".spectrum-Tags-item").contains("email") - cy.get(".spectrum-Tags-item").contains("profile") - }) - - it("Add a new scopes", () => { - cy.get(".spectrum-SideNav li").contains("Auth").click() - cy.location().should(loc => { - expect(loc.pathname).to.eq("/builder/portal/manage/auth") - }) - cy.get("div.content").scrollTo("bottom") - - cy.get("[data-cy=new-scope-input]").type("Sample{enter}") - cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 5) - cy.get(".spectrum-Tags-item").contains("Sample") - - cy.get(".auth-form input.spectrum-Textfield-input").type("Another ") - cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 6) - cy.get(".spectrum-Tags-item").contains("Another") - - cy.get("button[data-cy=oidc-save]").should("not.be.disabled"); - - cy.intercept("POST", "/api/global/configs").as("updateAuth") - cy.get("button[data-cy=oidc-save]").contains("Save").click({force: true}) - cy.wait("@updateAuth") - cy.get("@updateAuth").its("response.statusCode").should("eq", 200) - - cy.reload() - - cy.get("div.content").scrollTo("bottom") - - cy.get(".spectrum-Tags-item").contains("openid") - cy.get(".spectrum-Tags-item").contains("offline_access") - cy.get(".spectrum-Tags-item").contains("email") - cy.get(".spectrum-Tags-item").contains("profile") - cy.get(".spectrum-Tags-item").contains("Sample") - cy.get(".spectrum-Tags-item").contains("Another") - }) - - it("Should allow the removal of auth scopes", () => { - cy.get(".spectrum-SideNav li").contains("Auth").click() - cy.location().should(loc => { - expect(loc.pathname).to.eq("/builder/portal/manage/auth") - }) - cy.get("div.content").scrollTo("bottom") - - cy.get(".spectrum-Tags-item").contains("offline_access").parent().find(".spectrum-ClearButton").click() - cy.get(".spectrum-Tags-item").contains("profile").parent().find(".spectrum-ClearButton").click() - - cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4) - - cy.get(".spectrum-Tags-item").contains("offline_access").should("not.exist") - cy.get(".spectrum-Tags-item").contains("profile").should("not.exist") - - cy.get("button[data-cy=oidc-save]").should("not.be.disabled"); - - cy.intercept("POST", "/api/global/configs").as("updateAuth") - cy.get("button[data-cy=oidc-save]").contains("Save").click({force: true}) - cy.wait("@updateAuth") - cy.get("@updateAuth").its("response.statusCode").should("eq", 200) - - cy.get(".spectrum-Toast-content") - .contains("Settings saved") - .should("be.visible") - - cy.reload() - - cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4) - - cy.get(".spectrum-Tags-item").contains("offline_access").should("not.exist") - cy.get(".spectrum-Tags-item").contains("profile").should("not.exist") - }) - - it("Should allow auth scopes to be reset to the core defaults.", () => { - cy.get(".spectrum-SideNav li").contains("Auth").click() - - cy.get("div.content").scrollTo("bottom") - - cy.get("[data-cy=restore-oidc-default-scopes]").click({force: true}) - - cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4) - - cy.get(".spectrum-Tags-item").contains("openid") - cy.get(".spectrum-Tags-item").contains("offline_access") - cy.get(".spectrum-Tags-item").contains("email") - cy.get(".spectrum-Tags-item").contains("profile") - }) - - it("Should not allow invalid characters in the auth scopes", () => { - cy.get("[data-cy=new-scope-input]").type("thisIsInvalid\\{enter}") - cy.get(".spectrum-Form-itemField .error").contains("Auth scopes cannot contain spaces, double quotes or backslashes") - cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4) - - cy.get("[data-cy=new-scope-input]").clear() - - cy.get("[data-cy=new-scope-input]").type("alsoInvalid\"{enter}") - cy.get(".spectrum-Form-itemField .error").contains("Auth scopes cannot contain spaces, double quotes or backslashes") - cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4) - - cy.get("[data-cy=new-scope-input]").clear() - }) - - it("Should not allow duplicate auth scopes", () => { - cy.get("[data-cy=new-scope-input]").type("offline_access{enter}") - cy.get(".spectrum-Form-itemField .error").contains("Auth scope already exists") - cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4) - }) - - }) -}) \ No newline at end of file diff --git a/packages/builder/cypress/integration/adminAndManagement/userManagement.spec.js b/packages/builder/cypress/integration/adminAndManagement/userManagement.spec.js deleted file mode 100644 index 000ca7cb54..0000000000 --- a/packages/builder/cypress/integration/adminAndManagement/userManagement.spec.js +++ /dev/null @@ -1,236 +0,0 @@ -import filterTests from "../../support/filterTests" -const interact = require('../../support/interact') - -filterTests(["smoke", "all"], () => { - context("User Management", () => { - before(() => { - cy.login() - cy.deleteApp("Cypress Tests") - cy.createApp("Cypress Tests", false) - }) - - it("should create a user via basic onboarding", () => { - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000}) - cy.createUser("bbuser@test.com") - cy.get(interact.SPECTRUM_TABLE).should("contain", "bbuser") - }) - - it("should confirm App User role for a New User", () => { - cy.contains("bbuser").click() - cy.get(".spectrum-Form-itemField").eq(3).should('contain', 'App User') - - // User should not have app access - cy.get(interact.LIST_ITEMS, { timeout: 500 }).should("contain", "No apps") - }) - - if (Cypress.env("TEST_ENV")) { - xit("should assign role types", () => { - // 3 apps minimum required - to assign an app to each role type - cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`) - .its("body") - .then(val => { - if (val.length < 3) { - for (let i = 1; i < 3; i++) { - const uuid = () => Cypress._.random(0, 1e6) - const name = uuid() - if(i < 1){ - cy.createApp(name, false) - } else { - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000}) - cy.wait(1000) - cy.get(interact.CREATE_APP_BUTTON, { timeout: 2000 }).click({ force: true }) - cy.createAppFromScratch(name) - } - } - } - }) - // Navigate back to the user - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000}) - cy.get(interact.SPECTRUM_SIDENAV).contains("Users").click() - cy.get(interact.SPECTRUM_TABLE, { timeout: 1000 }).contains("bbuser").click() - cy.get(interact.SPECTRUM_HEADING).contains("bbuser", { timeout: 2000}) - for (let i = 0; i < 3; i++) { - cy.get(interact.SPECTRUM_TABLE, { timeout: 3000}) - .eq(1) - .find(interact.SPECTRUM_TABLE_ROW) - .eq(0) - .find(interact.SPECTRUM_TABLE_CELL) - .eq(0) - .click() - cy.get(interact.SPECTRUM_DIALOG_GRID, { timeout: 1000 }) - .contains("Choose an option") - .click() - .then(() => { - if (i == 0) { - cy.get(interact.SPECTRUM_MENU, { timeout: 2000 }).contains("Admin").click({ force: true }) - } - else if (i == 1) { - cy.get(interact.SPECTRUM_MENU, { timeout: 2000 }).contains("Power").click({ force: true }) - } - else if (i == 2) { - cy.get(interact.SPECTRUM_MENU, { timeout: 2000 }).contains("Basic").click({ force: true }) - } - cy.get(interact.SPECTRUM_BUTTON, { timeout: 2000 }) - .contains("Update role") - .click({ force: true }) - }) - cy.reload() - cy.wait(1000) - } - // Confirm roles exist within Configure roles table - cy.get(interact.SPECTRUM_TABLE, { timeout: 20000 }) - .eq(0) - .within(assginedRoles => { - expect(assginedRoles).to.contain("Admin") - expect(assginedRoles).to.contain("Power") - expect(assginedRoles).to.contain("Basic") - }) - }) - - xit("should unassign role types", () => { - // Set each app within Configure roles table to 'No Access' - cy.get(interact.SPECTRUM_TABLE) - .eq(0) - .find(interact.SPECTRUM_TABLE_ROW) - .its("length") - .then(len => { - for (let i = 0; i < len; i++) { - cy.get(interact.SPECTRUM_TABLE) - .eq(0) - .find(interact.SPECTRUM_TABLE_ROW) - .eq(0) - .find(interact.SPECTRUM_TABLE_CELL) - .eq(0) - .click() - .then(() => { - cy.get(interact.SPECTRUM_PICKER).eq(1).click({ force: true }) - cy.get(interact.SPECTRUM_POPOVER, { timeout: 500 }).contains("No Access").click() - }) - cy.get(interact.SPECTRUM_BUTTON) - .contains("Update role") - .click({ force: true }) - } - }) - // Confirm Configure roles table no longer has any apps in it - cy.get(interact.SPECTRUM_TABLE, { timeout: 1000 }).eq(0).contains("No rows found") - }) - } - - xit("should enable Developer access and verify application access", () => { - // Enable Developer access - cy.get(interact.FIELD) - .eq(4) - .within(() => { - cy.get(interact.SPECTRUM_SWITCH_INPUT).click({ force: true }) - }) - // No Access table should now be empty - cy.get(interact.CONTAINER) - .contains("No Access") - .parent() - .within(() => { - cy.get(interact.SPECTRUM_TABLE).contains("No rows found") - }) - - // Each app within Configure roles should have Admin access - cy.get(interact.SPECTRUM_TABLE) - .eq(0) - .find(interact.SPECTRUM_TABLE_ROW) - .its("length") - .then(len => { - for (let i = 0; i < len; i++) { - cy.get(interact.SPECTRUM_TABLE) - .eq(0) - .find(interact.SPECTRUM_TABLE_ROW) - .eq(i) - .contains("Admin") - cy.wait(500) - } - }) - }) - - xit("should disable Developer access and verify application access", () => { - // Disable Developer access - cy.get(interact.FIELD) - .eq(4) - .within(() => { - cy.get(".spectrum-Switch-input").click({ force: true }) - }) - // Configure roles table should now be empty - cy.get(interact.CONTAINER) - .contains("Configure roles") - .parent() - .within(() => { - cy.get(interact.SPECTRUM_TABLE).contains("No rows found") - }) - }) - - it("Should edit user details within user details page", () => { - // Add First name - cy.get(interact.FIELD, { timeout: 1000 }).eq(1).within(() => { - cy.wait(500) - cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 1000 }).wait(500).clear().click().type("bb") - }) - // Add Last name - cy.get(interact.FIELD, { timeout: 1000 }).eq(2).within(() => { - cy.wait(500) - cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 1000 }).click().wait(500).clear().type("test") - }) - cy.get(interact.FIELD, { timeout: 1000 }).eq(0).click() - // Reload page - cy.reload() - - // Confirm details have been saved - cy.get(interact.FIELD, { timeout: 20000 }).eq(1).within(() => { - cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', "bb") - }) - cy.get(interact.FIELD, { timeout: 1000 }).eq(2).within(() => { - cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 1000 }).should('have.value', "test") - }) - }) - - it("should reset the users password", () => { - cy.get(".title").within(() => { - cy.get(interact.SPECTRUM_ICON).click({ force: true }) - }) - cy.get(interact.SPECTRUM_MENU).within(() => { - cy.get(interact.SPECTRUM_MENU_ITEM).contains("Force password reset").click({ force: true }) - }) - - // Reset password modal - cy.get(interact.SPECTRUM_DIALOG_GRID) - .find(interact.SPECTRUM_TEXTFIELD_INPUT).invoke('val').as('pwd') - cy.get(interact.SPECTRUM_BUTTON).contains("Reset password").click({ force: true }) - cy.get(interact.SPECTRUM_BUTTON).contains("Reset password").should('not.exist') - - // Logout, then login with new password - cy.logOut() - cy.get('@pwd').then((pwd) => { - cy.login("bbuser@test.com", pwd) - }) - - // Reset password screen - for (let i = 0; i < 2; i++) { - cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).eq(i).type("test") - } - cy.get(interact.SPECTRUM_BUTTON).contains("Reset your password").click({ force: true }) - - // Confirm user logged in afer password change - cy.login("bbuser@test.com", "test") - cy.get(".avatar > .icon").click({ force: true }) - - cy.get(".spectrum-Menu-item").contains("Update user information").click({ force: true }) - cy.get(interact.SPECTRUM_TEXTFIELD_INPUT) - .eq(0) - .invoke('val').should('eq', 'bbuser@test.com') - - // Logout and login as previous user - cy.logoutNoAppGrid() - cy.login() - }) - - it("should delete a user", () => { - cy.deleteUser("bbuser@test.com") - cy.get(interact.SPECTRUM_TABLE, { timeout: 4000 }).should("not.have.text", "bbuser") - }) - }) -}) diff --git a/packages/builder/cypress/integration/adminAndManagement/userSettings.spec.js b/packages/builder/cypress/integration/adminAndManagement/userSettings.spec.js deleted file mode 100644 index a2b0d32d02..0000000000 --- a/packages/builder/cypress/integration/adminAndManagement/userSettings.spec.js +++ /dev/null @@ -1,109 +0,0 @@ -import filterTests from "../../support/filterTests" -const interact = require('../../support/interact') - -filterTests(["smoke", "all"], () => { - context("User Settings Menu", () => { - - before(() => { - cy.login() - }) - - it("should update user information via user settings menu", () => { - const fname = "test" - const lname = "user" - - cy.visit(`${Cypress.config().baseUrl}/builder`) - cy.updateUserInformation(fname, lname) - - // Go to user info and confirm name update - cy.contains("Users").click() - cy.contains("test@test.com").click() - - cy.get(interact.FIELD, { timeout: 1000 }).eq(1).within(() => { - cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', fname) - }) - cy.get(interact.FIELD).eq(2).within(() => { - cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', lname) - }) - }) - - it("should allow copying of the users API key", () => { - cy.get(".user-dropdown .avatar > .icon", { timeout: 2000 }).click({ force: true }) - cy.get(interact.SPECTRUM_MENU_ITEM).contains("View API key").click({ force: true }) - cy.get(interact.SPECTRUM_DIALOG_CONTENT).within(() => { - cy.get(interact.SPECTRUM_ICON).click({force: true}) - }) - // There may be timing issues with this on the smoke build - cy.wait(500) - cy.get(".spectrum-Toast-content") - .contains("URL copied to clipboard") - .should("be.visible") - }) - - it("should allow API key regeneration", () => { - // Get initial API key value - cy.get(interact.SPECTRUM_DIALOG_CONTENT) - .find(interact.SPECTRUM_TEXTFIELD_INPUT).invoke('val').as('keyOne') - - // Click re-generate key button - cy.get("button").contains("Re-generate key").click({ force: true }) - - // Verify API key was changed - cy.get(interact.SPECTRUM_DIALOG_CONTENT).within(() => { - cy.get('@keyOne').then((keyOne) => { - cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).invoke('val').should('not.eq', keyOne) - }) - }) - cy.closeModal() - }) - - it("should update password", () => { - // Access Update password modal - cy.get(".user-dropdown .avatar > .icon", { timeout: 2000 }).click({ force: true }) - cy.get(interact.SPECTRUM_MENU_ITEM).contains("Update password").click({ force: true }) - - // Enter new password and update - cy.get(interact.SPECTRUM_DIALOG_GRID).within(() => { - for (let i = 0; i < 2; i++) { - // password set to 'newpwd' - cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).eq(i).type("newpwd") - } - cy.get("button").contains("Update password").click({ force: true }) - }) - - // Logout & in with new password - //cy.logOut() - cy.login("test@test.com", "newpwd") - }) - - it("should open and close developer mode", () => { - cy.get(".user-dropdown .avatar > .icon", { timeout: 2000 }).click({ force: true }) - - // Close developer mode & verify - cy.get(interact.SPECTRUM_MENU_ITEM).contains("Close developer mode").click({ force: true }) - cy.get(interact.SPECTRUM_SIDENAV).should('not.exist') // No config sections - cy.get(interact.CREATE_APP_BUTTON).should('not.exist') // No create app button - cy.get(".app").should('not.exist') // At least one app should be available - - // Open developer mode & verify - cy.get(".avatar > .icon").click({ force: true }) - cy.get(interact.SPECTRUM_MENU_ITEM).contains("Open developer mode").click({ force: true }) - cy.get(interact.SPECTRUM_SIDENAV).should('exist') // config sections available - cy.get(interact.CREATE_APP_BUTTON).should('exist') // create app button available - }) - - after(() => { - // Change password back to original value - cy.get(".user-dropdown .avatar > .icon", { timeout: 2000 }).click({ force: true }) - cy.get(interact.SPECTRUM_MENU_ITEM).contains("Update password").click({ force: true }) - cy.get(interact.SPECTRUM_DIALOG_GRID).within(() => { - for (let i = 0; i < 2; i++) { - cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).eq(i).type("test") - } - cy.get("button").contains("Update password").click({ force: true }) - }) - // Remove users name - cy.updateUserInformation() - }) - }) -}) diff --git a/packages/builder/cypress/integration/appOverview.spec.js b/packages/builder/cypress/integration/appOverview.spec.js deleted file mode 100644 index d7f2882b26..0000000000 --- a/packages/builder/cypress/integration/appOverview.spec.js +++ /dev/null @@ -1,441 +0,0 @@ -import filterTests from "../support/filterTests" -import clientPackage from "@budibase/client/package.json" - -filterTests(["all"], () => { - context("Application Overview screen", () => { - before(() => { - cy.login() - cy.deleteAllApps() - cy.createApp("Cypress Tests") - }) - - it("Should be accessible from the applications list", () => { - cy.visit(`${Cypress.config().baseUrl}/builder`) - cy.get(".appTable .title") - .eq(0) - .invoke("attr", "data-cy") - .then($dataCy => { - const dataCy = $dataCy - cy.get(".appTable .app-row-actions button") - .contains("Manage") - .click({ force: true }) - - cy.location().should(loc => { - expect(loc.pathname).to.eq("/builder/portal/overview/" + dataCy) - }) - }) - }) - - // Find a more suitable place for this. - it("Should allow unlocking in the app list", () => { - cy.visit(`${Cypress.config().baseUrl}/builder`) - - cy.get(".appTable .lock-status").eq(0).contains("Locked by you").click() - - cy.unlockApp({ owned: true }) - - cy.get(".appTable").should("exist") - cy.get(".lock-status").should("not.be.visible") - }) - - it("Should allow unlocking in the app overview screen", () => { - cy.visit(`${Cypress.config().baseUrl}/builder`) - - cy.get(".appTable .app-row-actions button") - .contains("Edit") - .eq(0) - .click({ force: true }) - cy.wait(1000) - cy.visit(`${Cypress.config().baseUrl}/builder`) - cy.get(".appTable .app-row-actions button") - .contains("Manage") - .eq(0) - .click({ force: true }) - cy.get(".lock-status").eq(0).contains("Locked by you").click() - - cy.unlockApp({ owned: true }) - - cy.get(".lock-status").should("not.be.visible") - }) - - it("Should reflect the deploy state of an app that hasn't been published.", () => { - cy.visit(`${Cypress.config().baseUrl}/builder`) - - cy.get(".appTable .app-row-actions button") - .contains("Manage") - .eq(0) - .click({ force: true }) - cy.get(".header-right button.spectrum-Button[data-cy='view-app']").should( - "be.disabled" - ) - - cy.get(".spectrum-Tabs-item.is-selected").contains("Overview") - cy.get(".overview-tab").should("be.visible") - - cy.get(".overview-tab [data-cy='app-status']").within(() => { - cy.get(".status-display").contains("Unpublished") - cy.get(".status-display .icon svg[aria-label='GlobeStrike']").should( - "exist" - ) - cy.get(".status-text").contains("-") - }) - }) - - it("Should reflect the app deployment state", () => { - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 }) - cy.get(".appTable .app-row-actions button") - .contains("Edit") - .eq(0) - .click({ force: true }) - - cy.wait(500) - cy.get(".toprightnav button.spectrum-Button", { timeout: 2000 }) - .contains("Publish") - .click({ force: true }) - cy.get(".spectrum-Modal [data-cy='deploy-app-modal']") - .should("be.visible") - .within(() => { - cy.get(".spectrum-Button").contains("Publish").click({ force: true }) - cy.wait(1000) - }) - - cy.visit(`${Cypress.config().baseUrl}/builder`) - cy.get(".appTable .app-row-actions button") - .contains("Manage") - .eq(0) - .click({ force: true }) - cy.get(".header-right button.spectrum-Button[data-cy='view-app']").should( - "not.be.disabled" - ) - - cy.get(".overview-tab [data-cy='app-status']").within(() => { - cy.get(".status-display").contains("Published") - cy.get(".status-display .icon svg[aria-label='GlobeCheck']").should( - "exist" - ) - cy.get(".status-text").contains("Last published a few seconds ago") - }) - }) - - it("Should reflect an application that has been unpublished", () => { - cy.visit(`${Cypress.config().baseUrl}/builder`) - cy.get(".appTable .app-row-actions button") - .contains("Edit") - .eq(0) - .click({ force: true }) - - cy.get(".deployment-top-nav svg[aria-label='Globe']").click({ - force: true, - }) - - cy.get("[data-cy='publish-popover-menu']").should("be.visible") - cy.get( - "[data-cy='publish-popover-menu'] [data-cy='publish-popover-action']" - ).click({ force: true }) - - cy.get("[data-cy='unpublish-modal']") - .should("be.visible") - .within(() => { - cy.get(".confirm-wrap button").click({ force: true }) - }) - - cy.visit(`${Cypress.config().baseUrl}/builder`) - cy.get(".appTable .app-row-actions button") - .contains("Manage") - .eq(0) - .click({ force: true }) - cy.get(".overview-tab [data-cy='app-status']").within(() => { - cy.get(".status-display").contains("Unpublished") - cy.get(".status-display .icon svg[aria-label='GlobeStrike']").should( - "exist" - ) - cy.get(".status-text").contains("Last published a few seconds ago") - }) - }) - - it("Should allow the editing of the application icon and colour", () => { - cy.visit(`${Cypress.config().baseUrl}/builder`) - cy.get(".appTable .app-row-actions button") - .contains("Manage") - .eq(0) - .click({ force: true }) - cy.get(".edit-hover", { timeout: 1000 }).eq(0).click({ force: true }) - // Select random icon - cy.wait(400) - cy.get(".grid").within(() => { - cy.get(".icon-item") - .eq(Math.floor(Math.random() * 23) + 1) - .click() - }) - // Select random colour - cy.get(".fill").click() - cy.get(".colors").within(() => { - cy.get(".color") - .eq(Math.floor(Math.random() * 33) + 1) - .click() - }) - cy.intercept("**/applications/**").as("iconChange") - cy.get(".spectrum-Button").contains("Save").click({ force: true }) - cy.wait("@iconChange") - cy.get("@iconChange").its("response.statusCode").should("eq", 200) - // Confirm icon has changed from default - // Confirm colour has been applied - cy.get(".spectrum-ActionButton-label").contains("Back").click({ force: true }) - cy.get(".appTable", { timeout: 2000 }).within(() => { - cy.get("[aria-label]") - .eq(0) - .children() - .should("have.attr", "xlink:href") - .and("not.contain", "#spectrum-icon-18-Apps") - cy.get(".title") - .children() - .children() - .should("have.attr", "style") - .and("contains", "color") - }) - }) - - it("Should reflect the last time the application was edited", () => { - cy.visit(`${Cypress.config().baseUrl}/builder`) - cy.get(".appTable .app-row-actions button") - .contains("Manage") - .eq(0) - .click({ force: true }) - cy.get(".header-right button").contains("Edit").click({ force: true }) - - cy.navigateToFrontend() - - cy.searchAndAddComponent("Headline").then(componentId => { - cy.getComponent(componentId).should("exist") - }) - - cy.visit(`${Cypress.config().baseUrl}/builder`) - cy.get(".appTable .app-row-actions button") - .contains("Manage") - .eq(0) - .click({ force: true }) - cy.get(".overview-tab [data-cy='edited-by']").within(() => { - cy.get(".editor-name").contains("You") - cy.get(".last-edit-text").contains("Last edited a few seconds ago") - }) - }) - - it("Should reflect application version is up-to-date", () => { - cy.visit(`${Cypress.config().baseUrl}/builder`) - cy.get(".appTable .app-row-actions button") - .contains("Manage") - .eq(0) - .click({ force: true }) - cy.get(".overview-tab [data-cy='app-version']").within(() => { - cy.get(".version-status").contains("You're running the latest!") - }) - }) - - it("Should navigate to the settings tab when clicking the App Version card header", () => { - cy.visit(`${Cypress.config().baseUrl}/builder`) - cy.get(".appTable .app-row-actions button") - .contains("Manage") - .eq(0) - .click({ force: true }) - cy.get(".spectrum-Tabs-item.is-selected").contains("Overview") - cy.get(".overview-tab").should("be.visible") - - cy.get(".overview-tab [data-cy='app-version'] .dash-card-header").click({ - force: true, - }) - - cy.get(".spectrum-Tabs-item.is-selected").contains("Settings") - cy.get(".settings-tab").should("be.visible") - cy.get(".overview-tab").should("not.exist") - }) - - it("Should allow the upgrading of an application, if available.", () => { - cy.visit(`${Cypress.config().baseUrl}/builder`) - cy.get(".appTable .app-row-actions button") - .contains("Manage") - .eq(0) - .click({ force: true }) - cy.wait(500) - - cy.location().then(loc => { - const params = loc.pathname.split("/") - const appId = params[params.length - 1] - cy.log(appId) - //Downgrade the app for the test - cy.alterAppVersion(appId, "0.0.1-alpha.0").then(() => { - cy.reload() - cy.log("Current deployment version: " + clientPackage.version) - - cy.get(".version-status a", { timeout: 5000 }).contains("Update").click() - cy.get(".spectrum-Tabs-item.is-selected").contains("Settings") - - cy.get(".version-section .page-action button") - .contains("Update") - .click({ force: true }) - - cy.intercept("POST", "**/applications/**/client/update").as( - "updateVersion" - ) - cy.get(".spectrum-Modal.is-open button") - .contains("Update") - .click({ force: true }) - - cy.wait("@updateVersion") - .its("response.statusCode") - .should("eq", 200) - .then(() => { - cy.visit(`${Cypress.config().baseUrl}/builder`) - cy.get(".appTable .app-row-actions button") - .contains("Manage") - .eq(0) - .click({ force: true }) - cy.get(".spectrum-Tabs-item") - .contains("Overview") - .click({ force: true }) - cy.get(".overview-tab [data-cy='app-version']").within(() => { - cy.get(".spectrum-Heading").contains(clientPackage.version) - cy.get(".version-status").contains("You're running the latest!") - }) - }) - }) - }) - }) - - it("Should allow editing of the app details.", () => { - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 }) - cy.get(".appTable .app-row-actions button") - .contains("Manage") - .eq(0) - .click({ force: true }) - cy.get(".spectrum-Tabs-item").contains("Settings").click() - cy.get(".spectrum-Tabs-item.is-selected").contains("Settings") - cy.get(".settings-tab").should("be.visible") - - cy.get(".details-section .page-action button") - .contains("Edit") - .click({ force: true }) - cy.updateAppName("sample name") - - //publish and check its disabled - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 }) - cy.wait(500) - cy.get(".appTable .app-row-actions button") - .contains("Edit") - .eq(0) - .click({ force: true }) - - cy.get(".toprightnav button.spectrum-Button") - .contains("Publish") - .click({ force: true }) - cy.get(".spectrum-Modal [data-cy='deploy-app-modal']") - .should("be.visible") - .within(() => { - cy.get(".spectrum-Button").contains("Publish").click({ force: true }) - cy.wait(1000) - }) - - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 10000 }) - cy.get(".appTable .app-row-actions button", { timeout: 5000 }) - .contains("Manage") - .eq(0) - .click({ force: true }) - cy.get(".spectrum-Tabs-item").contains("Settings").click() - cy.get(".spectrum-Tabs-item.is-selected").contains("Settings") - - cy.get(".details-section .page-action .spectrum-Button").scrollIntoView() - cy.get(".details-section .page-action .spectrum-Button", { timeout: 1000 }).should( - "be.disabled" - ) - }) - - xit("Should allow copying of the published application Id", () => { - cy.visit(`${Cypress.config().baseUrl}/builder`) - cy.get(".appTable .app-row-actions") - .eq(0) - .within(() => { - cy.get(".spectrum-Button").contains("Edit").click({ force: true }) - }) - - cy.publishApp("sample-name") - - cy.visit(`${Cypress.config().baseUrl}/builder`) - cy.get(".appTable .app-row-actions button") - .contains("Manage") - .eq(0) - .click({ force: true }) - cy.get(".app-overview-actions-icon > .icon").click({ force: true }) - - cy.get("[data-cy='app-overview-menu-popover']") - .eq(0) - .within(() => { - cy.get(".spectrum-Menu-item") - .contains("Copy App ID") - .click({ force: true }) - }) - - cy.get(".spectrum-Toast-content") - .contains("App ID copied to clipboard.") - .should("be.visible") - }) - - it("Should allow unpublishing of the application via the Unpublish link", () => { - cy.visit(`${Cypress.config().baseUrl}/builder`) - cy.get(".appTable .app-row-actions button") - .contains("Manage") - .eq(0) - .click({ force: true }) - - cy.get(`[data-cy="app-status"]`).within(() => { - cy.contains("Unpublish").click({ force: true }) - }) - - cy.get("[data-cy='unpublish-modal']") - .should("be.visible") - .within(() => { - cy.get(".confirm-wrap button").click({ force: true }) - }) - - cy.get(".overview-tab [data-cy='app-status']").within(() => { - cy.get(".status-display").contains("Unpublished") - cy.get(".status-display .icon svg[aria-label='GlobeStrike']") - .should("exist") - }) - }) - - it("Should allow deleting of the application", () => { - cy.visit(`${Cypress.config().baseUrl}/builder`) - cy.get(".appTable .app-row-actions button") - .contains("Manage") - .eq(0) - .click({ force: true }) - cy.get(".app-overview-actions-icon > .icon").click({ force: true }) - - cy.get("[data-cy='app-overview-menu-popover']") - .eq(0) - .within(() => { - cy.get(".spectrum-Menu-item") - .contains("Delete") - .click({ force: true }) - cy.wait(500) - }) - - //The test application was renamed earlier in the spec - cy.get(".spectrum-Dialog-grid").within(() => { - cy.get("input").type("sample name") - cy.get(".spectrum-Button--warning").click() - }) - - cy.location().should(loc => { - expect(loc.pathname).to.eq("/builder/portal/apps") - }) - - cy.get(".appTable").should("not.exist") - - cy.get(".welcome .container h1").contains("Let's create your first app!") - }) - - after(() => { - cy.deleteAllApps() - }) - }) -}) diff --git a/packages/builder/cypress/integration/appPublishWorkflow.spec.js b/packages/builder/cypress/integration/appPublishWorkflow.spec.js deleted file mode 100644 index 0e3fbb191b..0000000000 --- a/packages/builder/cypress/integration/appPublishWorkflow.spec.js +++ /dev/null @@ -1,109 +0,0 @@ -import filterTests from "../support/filterTests" -import { APP_TABLE_APP_NAME, DEPLOY_SUCCESS_MODAL } from "../support/interact"; -const interact = require('../support/interact') - -filterTests(['all'], () => { - context("Publish Application Workflow", () => { - before(() => { - cy.login() - cy.deleteAllApps() - cy.createApp("Cypress Tests", false) - }) - - it("Should reflect the unpublished status correctly", () => { - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 }) - - cy.get(interact.APP_TABLE_STATUS, { timeout: 3000 }).eq(0) - .within(() => { - cy.contains("Unpublished") - cy.get(interact.GLOBESTRIKE).should("exist") - }) - - cy.get(interact.APP_TABLE_ROW_ACTION).eq(0) - .within(() => { - cy.get(interact.SPECTRUM_BUTTON_TEMPLATE).contains("Edit").click({ force: true }) - }) - - cy.get(interact.DEPLOYMENT_TOP_NAV_GLOBESTRIKE).should("exist") - cy.get(interact.DEPLOYMENT_TOP_GLOBE).should("not.exist") - }) - - it("Should publish an application and correctly reflect that", () => { - //Assuming the previous test was run and the unpublished app is open in edit mode. - cy.closeModal() - cy.get(interact.TOPRIGHTNAV_BUTTON_SPECTRUM).contains("Publish").click({ force : true }) - - cy.get(interact.DEPLOY_APP_MODAL).should("be.visible") - .within(() => { - cy.get(interact.SPECTRUM_BUTTON).contains("Publish").click({ force : true }) - }); - - //Verify that the app url is presented correctly to the user - cy.get(interact.DEPLOY_SUCCESS_MODAL, { timeout: 1000 }) - .should("be.visible") - .within(() => { - let appUrl = Cypress.config().baseUrl + '/app/cypress-tests' - cy.get(interact.DEPLOY_APP_URL_INPUT).should('have.value', appUrl) - cy.get(interact.SPECTRUM_BUTTON).contains("Done").click({ force: true }) - }) - - cy.visit(`${Cypress.config().baseUrl}/builder`) - - cy.get(interact.APP_TABLE_STATUS, { timeout: 3000 }).eq(0) - .within(() => { - cy.contains("Published") - cy.get(interact.GLOBE).should("exist") - }) - - cy.get(interact.APP_TABLE_ROW_ACTION).eq(0) - .within(() => { - cy.get(interact.SPECTRUM_BUTTON).contains("Manage") - cy.get(interact.SPECTRUM_BUTTON).contains("Edit").click({ force: true }) - }) - - cy.get(interact.DEPLOYMENT_TOP_GLOBE).should("exist").click({ force: true }) - - cy.get(interact.PUBLISH_POPOVER_MENU).should("be.visible") - .within(() => { - cy.get(interact.PUBLISH_POPOVER_ACTION).should("exist") - cy.get("button").contains("View app").should("exist") - cy.get(interact.PUBLISH_POPOVER_MESSAGE).should("have.text", "Last published a few seconds ago") - }) - }) - - it("Should unpublish an application using the link and reflect the status change", () => { - //Assuming the previous test app exists and is published - - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 }) - - cy.get(interact.APP_TABLE_STATUS).eq(0) - .within(() => { - cy.contains("Published") - cy.get("svg[aria-label='Globe']").should("exist") - }) - - cy.get(interact.APP_TABLE).eq(0) - .within(() => { - cy.get(interact.APP_TABLE_APP_NAME).click({ force: true }) - }) - - cy.closeModal() - cy.get(interact.DEPLOYMENT_TOP_GLOBE).should("exist").click({ force: true }) - - cy.get("[data-cy='publish-popover-menu']") - .within(() => { - cy.get(interact.PUBLISH_POPOVER_ACTION).click({ force: true }) - }) - - cy.get(interact.UNPUBLISH_MODAL).should("be.visible") - .within(() => { - cy.get(interact.CONFIRM_WRAP_BUTTON).click({ force: true } - )}) - - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 6000 }) - cy.wait(500) - cy.get(interact.APP_TABLE_STATUS, { timeout: 10000 }).eq(0).contains("Unpublished") - - }) - }) -}) diff --git a/packages/builder/cypress/integration/autoScreensUI.spec.js b/packages/builder/cypress/integration/autoScreensUI.spec.js deleted file mode 100644 index 0253675c5b..0000000000 --- a/packages/builder/cypress/integration/autoScreensUI.spec.js +++ /dev/null @@ -1,100 +0,0 @@ -import filterTests from "../support/filterTests" -const interact = require('../support/interact') - -filterTests(['smoke', 'all'], () => { - context("Auto Screens UI", () => { - before(() => { - cy.login() - cy.deleteAllApps() - }) - - it("should disable the autogenerated screen options if no sources are available", () => { - cy.createApp("First Test App", false) - cy.closeModal(); - - cy.navigateToAutogeneratedModal() - cy.get(interact.CONFIRM_WRAP_SPE_BUTTON).should('be.disabled') - - cy.deleteAllApps() - }); - - it("should not display incompatible sources", () => { - cy.createApp("Test App") - - cy.selectExternalDatasource("REST") - cy.selectExternalDatasource("S3") - cy.get(interact.SPECTRUM_MODAL).within(() => { - cy.get(interact.SPECTRUM_BUTTON).contains("Save and continue to query").click({ force : true }) - }) - - cy.navigateToAutogeneratedModal() - - cy.get(interact.DATA_SOURCE_ENTRY).should('have.length', 1) - cy.get(interact.DATA_SOURCE_ENTRY) - - cy.deleteAllApps() - }); - - it("should generate internal table screens", () => { - cy.createTestApp() - // Create Autogenerated screens from the internal table - cy.createDatasourceScreen(["Cypress Tests"]) - // Confirm screens have been auto generated - cy.get(interact.BODY).should('contain', "cypress-tests") - .and('contain', 'cypress-tests/:id') - .and('contain', 'cypress-tests/new/row') - }) - - it("should generate multiple internal table screens at once", () => { - const initialTable = "Cypress Tests" - const secondTable = "Table Two" - // Create a second internal table - cy.createTable(secondTable) - // Create Autogenerated screens from the internal tables - cy.createDatasourceScreen([initialTable, secondTable]) - // Confirm screens have been auto generated - // Previously generated tables are suffixed with numbers - as expected - cy.get(interact.BODY).should('contain', 'cypress-tests-2') - .and('contain', 'cypress-tests-2/:id') - .and('contain', 'cypress-tests-2/new/row') - .and('contain', 'table-two') - .and('contain', 'table-two/:id') - .and('contain', 'table-two/new/row') - }) - - it("should generate multiple internal table screens with the same screen access level", () => { - //The tables created in the previous step still exist - cy.createTable("Table Three") - cy.createTable("Table Four") - cy.createDatasourceScreen(["Table Three", "Table Four"], "Admin") - - // Filter screens to Admin - cy.filterScreensAccessLevel('Admin') - - cy.get(interact.BODY).should('contain', 'table-three') - .and('contain', 'table-three/:id') - .and('contain', 'table-three/new/row') - .and('contain', 'table-four') - .and('contain', 'table-four/:id') - .and('contain', 'table-four/new/row') - .and('not.contain', 'table-two') - .and('not.contain', 'cypress-tests') - }) - - if (Cypress.env("TEST_ENV")) { - it("should generate datasource screens", () => { - // Using MySQL datasource for testing this - const datasource = "MySQL" - // Select & configure MySQL datasource - cy.selectExternalDatasource(datasource) - cy.addDatasourceConfig(datasource) - // Create Autogenerated screens from a MySQL table - MySQL contains books table - cy.createDatasourceScreen(["books"]) - - cy.get(interact.BODY).should('contain', 'books') - .and('contain', 'books/:id') - .and('contain', 'books/new/row') - }) - } - }) -}) diff --git a/packages/builder/cypress/integration/createApp.spec.js b/packages/builder/cypress/integration/createApp.spec.js deleted file mode 100644 index 179741e21a..0000000000 --- a/packages/builder/cypress/integration/createApp.spec.js +++ /dev/null @@ -1,221 +0,0 @@ -import filterTests from '../support/filterTests' -const interact = require('../support/interact') - -filterTests(['smoke', 'all'], () => { - context("Create an Application", () => { - - before(() => { - cy.login() - cy.deleteAllApps() - }) - - if (!(Cypress.env("TEST_ENV"))) { - it("should show the new user UI/UX", () => { - cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/create`, { timeout: 5000 }) //added /portal/apps/create - cy.wait(1000) - cy.get(interact.CREATE_APP_BUTTON, { timeout: 10000 }).contains('Start from scratch').should("exist") - - cy.get(interact.TEMPLATE_CATEGORY_FILTER).should("exist") - cy.get(interact.TEMPLATE_CATEGORY).should("exist") - - cy.get(interact.APP_TABLE).should("not.exist") - }) - } - - it("should provide filterable templates", () => { - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 }) - cy.wait(500) - - cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`) - .its("body") - .then(val => { - if (val.length > 0) { - cy.get(interact.SPECTRUM_BUTTON).contains("Templates").click({force: true}) - } - }) - - cy.get(interact.TEMPLATE_CATEGORY_FILTER).should("exist") - cy.get(interact.TEMPLATE_CATEGORY).should("exist") - - cy.get(interact.TEMPLATE_CATEGORY_ACTIONGROUP).its('length').should('be.gt', 1) - cy.get(interact.TEMPLATE_CATEGORY_FILTER_ACTIONBUTTON).its('length').should('be.gt', 2) - - cy.get(interact.TEMPLATE_CATEGORY_FILTER_ACTIONBUTTON).eq(1).click() - cy.get(interact.TEMPLATE_CATEGORY_ACTIONGROUP).should('have.length', 1) - - cy.get(interact.TEMPLATE_CATEGORY_FILTER_ACTIONBUTTON).eq(0).click() - cy.get(interact.TEMPLATE_CATEGORY_ACTIONGROUP).its('length').should('be.gt', 1) - }) - - it("should enforce a valid url before submission", () => { - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 10000 }) - - // Start create app process. If apps already exist, click second button - cy.wait(1000) - cy.get(interact.CREATE_APP_BUTTON, { timeout: 3000 }).click({ force: true }) - - const appName = "Cypress Tests" - cy.get(interact.SPECTRUM_MODAL).within(() => { - - cy.get(interact.APP_NAME_INPUT).eq(0).should('have.focus') - - //Auto fill - cy.get(interact.APP_NAME_INPUT).eq(0).clear() - cy.get(interact.APP_NAME_INPUT).eq(0).type(appName).should("have.value", appName).blur() - cy.get(interact.APP_NAME_INPUT).eq(1).should("have.value", "/cypress-tests") - cy.get(interact.SPECTRUM_BUTTON_GROUP).contains("Create app").should('not.be.disabled') - - //Empty the app url - disabled create - cy.get(interact.APP_NAME_INPUT).eq(1).clear().blur() - cy.get(interact.SPECTRUM_BUTTON_GROUP).contains("Create app").should('be.disabled') - - //Invalid url - cy.get(interact.APP_NAME_INPUT).eq(1).type("/new app-url").blur() - cy.get(interact.SPECTRUM_BUTTON_GROUP).contains("Create app").should('be.disabled') - - //Specifically alter the url - cy.get(interact.APP_NAME_INPUT).eq(1).clear() - cy.get(interact.APP_NAME_INPUT).eq(1).type("another-app-name").blur() - cy.get(interact.APP_NAME_INPUT).eq(1).should("have.value", "/another-app-name") - cy.get(interact.APP_NAME_INPUT).eq(0).should("have.value", appName) - cy.get(interact.SPECTRUM_BUTTON_GROUP).contains("Create app").should('not.be.disabled') - - }) - }) - - it("should create the first application from scratch", () => { - const appName = "Cypress Tests" - cy.createApp(appName, false) - - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 }) - - cy.applicationInAppTable(appName) - cy.deleteApp(appName) - }) - - it("should create the first application from scratch with a default name", () => { - cy.updateUserInformation("", "") - cy.createApp("", false) - cy.applicationInAppTable("My app") - cy.deleteApp("My app") - }) - - it("should create the first application from scratch, using the users first name as the default app name", () => { - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 }) - - cy.updateUserInformation("Ted", "Userman") - - cy.createApp("", false) - cy.applicationInAppTable("Teds app") - cy.deleteApp("Teds app") - - // Accomodate names that end in 'S' - cy.updateUserInformation("Chris", "Userman") - - cy.createApp("", false) - cy.applicationInAppTable("Chris app") - cy.deleteApp("Chris app") - - cy.updateUserInformation("", "") - }) - - it("should create an application from an export", () => { - const exportedApp = 'cypress/fixtures/exported-app.txt' - - cy.importApp(exportedApp, "") - - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 2000 }) - - cy.applicationInAppTable("My app") - - cy.get(".appTable .name").eq(0).click() - - cy.deleteApp("My app") - }) - - it("should create an application from an export, using the users first name as the default app name", () => { - const exportedApp = 'cypress/fixtures/exported-app.txt' - - cy.updateUserInformation("Ted", "Userman") - - cy.importApp(exportedApp, "") - - cy.visit(`${Cypress.config().baseUrl}/builder`) - - cy.applicationInAppTable("Teds app") - - cy.get(".appTable .name").eq(0).click() - - cy.deleteApp("Teds app") - - cy.updateUserInformation("", "") - }) - - it("should generate the first application from a template", () => { - cy.visit(`${Cypress.config().baseUrl}/builder`) - cy.wait(500) - - // Navigate to Create new app section if apps already exist - cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`) - .its("body") - .then(val => { - if (val.length > 0) { - cy.get(interact.CREATE_APP_BUTTON).click({ force: true }) - } - }) - - cy.get(interact.TEMPLATE_CATEGORY_FILTER).should("exist") - cy.get(interact.TEMPLATE_CATEGORY).should("exist") - - // Select template - cy.get(interact.TEMPLATE_CATEGORY_ACTIONGROUP).eq(0).within(() => { - const card = cy.get('.template-card').eq(0).should("exist"); - const cardOverlay = card.get('.template-thumbnail-action-overlay').should("exist") - cardOverlay.invoke("show") - cardOverlay.get("button").contains("Use template").should("exist").click({force: true}) - }) - - // CMD Create app from theme card - cy.get(".spectrum-Modal").should('be.visible') - - const templateName = cy.get(".spectrum-Modal .template-thumbnail-text") - templateName.invoke('text') - .then(templateNameText => { - const templateNameParsed = "/"+templateNameText.toLowerCase().replace(/\s+/g, "-") - cy.get(interact.SPECTRUM_MODAL_INPUT).eq(0).should("have.value", templateNameText) - cy.get(interact.SPECTRUM_MODAL_INPUT).eq(1).should("have.value", templateNameParsed) - - cy.get(".spectrum-Modal .spectrum-ButtonGroup").contains("Create app").click() - cy.wait(5000) - - cy.visit(`${Cypress.config().baseUrl}/builder`) - cy.wait(2000) - - cy.applicationInAppTable(templateNameText) - cy.deleteApp(templateNameText) - }); - - }) - - it("should display a second application and app filtering", () => { - // Create first app - const appName = "Cypress Tests" - cy.createApp(appName) - - cy.visit(`${Cypress.config().baseUrl}/builder`) - - // Create second app - const secondAppName = "Second App Demo" - cy.createApp(secondAppName) - - cy.visit(`${Cypress.config().baseUrl}/builder`) - - //Both applications should exist and be searchable - cy.searchForApplication(appName) - cy.searchForApplication(secondAppName) - - cy.deleteApp(secondAppName) - }) - - }) -}) diff --git a/packages/builder/cypress/integration/createAutomation.spec.js b/packages/builder/cypress/integration/createAutomation.spec.js deleted file mode 100644 index b5ff406297..0000000000 --- a/packages/builder/cypress/integration/createAutomation.spec.js +++ /dev/null @@ -1,64 +0,0 @@ -import filterTests from "../support/filterTests" -const interact = require('../support/interact') - -filterTests(['smoke', 'all'], () => { - context("Create a automation", () => { - before(() => { - cy.login() - cy.createTestApp() - }) - - it("should create a automation", () => { - cy.createTestTableWithData() - cy.wait(2000) - cy.contains("Automate").click() - cy.get(interact.ADD_BUTTON_SPECTRUM).click() - cy.get(interact.MODAL_INNER_WRAPPER).within(() => { - cy.get("input").type("Add Row") - cy.contains("Row Created").click({ force: true }) - cy.get(interact.SPECTRUM_BUTTON_CTA, { timeout: 500 }).click() - }) - - // Setup trigger - cy.get(interact.SPECTRUM_PICKER_LABEL).click() - cy.wait(500) - cy.contains("dog").click() - // Create action - cy.get('[aria-label="AddCircle"]', { timeout: 2000 }).eq(1).click() - cy.get(interact.MODAL_INNER_WRAPPER).within(() => { - cy.wait(1000) - cy.contains("Create Row").trigger('mouseover').click().click() - cy.get(interact.SPECTRUM_BUTTON_CTA).click() - }) - cy.get(interact.SPECTRUM_PICKER_LABEL).eq(1).click() - cy.contains("dog").click() - cy.get(interact.SPECTRUM_TEXTFIELD_INPUT) - .first() - .type("{{ trigger.row.name }}", { parseSpecialCharSequences: false }) - cy.get(interact.SPECTRUM_TEXTFIELD_INPUT) - .eq(1) - .type("11") - cy.contains("Finish and test automation").click() - - cy.get(interact.MODAL_INNER_WRAPPER).within(() => { - cy.get(interact.SPECTRUM_PICKER_LABEL, { timeout: 1000 }).click() - cy.contains("dog").click() - cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 1000 }) - .first() - .type("automationGoodboy") - cy.get(interact.SPECTRUM_TEXTFIELD_INPUT) - .eq(1) - .type("11") - cy.get(interact.SPECTRUM_TEXTFIELD_INPUT) - .eq(2) - .type("123456") - cy.get(interact.SPECTRUM_TEXTFIELD_INPUT) - .eq(3) - .type("123456") - cy.contains("Test").click() - }) - cy.contains("Data").click() - cy.contains("automationGoodboy") - }) - }) -}) diff --git a/packages/builder/cypress/integration/createBinding.spec.js b/packages/builder/cypress/integration/createBinding.spec.js deleted file mode 100644 index 0c1ddf1e7d..0000000000 --- a/packages/builder/cypress/integration/createBinding.spec.js +++ /dev/null @@ -1,64 +0,0 @@ -import filterTests from "../support/filterTests" - -filterTests(['smoke', 'all'], () => { - context("Create Bindings", () => { - before(() => { - cy.login() - cy.createTestApp() - cy.navigateToFrontend() - }) - - it("should add a current user binding", () => { - cy.searchAndAddComponent("Paragraph").then(() => { - addSettingBinding("text", ["Current User", "_id"], "Current User._id") - }) - }) - - it("should handle an invalid binding", () => { - cy.searchAndAddComponent("Paragraph").then(componentId => { - // Cypress needs to escape curly brackets - cy.get("[data-cy=setting-text] input") - .type("{{}{{}{{} Current User._id {}}{}}") - .blur() - cy.getComponent(componentId).should("have.text", "{{{ [user].[_id] }}") - }) - }) - - xit("should add a URL param binding", () => { - const paramName = "foo" - cy.createScreen(`/test/:${paramName}`) - cy.searchAndAddComponent("Paragraph").then(componentId => { - addSettingBinding("text", ["URL", paramName], `URL.${paramName}`) - // The builder preview pages don't have a real URL, so all we can do - // is check that we were able to bind to the property, and that the - // component exists on the page - cy.getComponent(componentId).should("have.text", "New Paragraph") - }) - }) - - it("should add a binding with a handlebars helper", () => { - cy.searchAndAddComponent("Paragraph").then(componentId => { - // Cypress needs to escape curly brackets - cy.get("[data-cy=setting-text] input") - .type("{{}{{} add 1 2 {}}{}}") - .blur() - cy.getComponent(componentId).should("have.text", "3") - }) - }) - }) - - const addSettingBinding = (setting, bindingCategories, bindingText, clickOption = true) => { - cy.get(`[data-cy="setting-${setting}"] [data-cy=text-binding-button]`).click() - cy.get(".category-list li").contains(bindingCategories[0]) - cy.get(".drawer").within(() => { - if (clickOption) { - cy.get(".category-list li").contains(bindingCategories[0]).click() - cy.get("li.binding").contains(bindingCategories[1]).click() - cy.get("textarea").should("have.value", `{{ ${bindingText} }}`) - } else { - cy.get("textarea").type(bindingText) - } - cy.contains("Save").click() - }) - } -}) diff --git a/packages/builder/cypress/integration/createComponents.spec.js b/packages/builder/cypress/integration/createComponents.spec.js deleted file mode 100644 index 7f29466258..0000000000 --- a/packages/builder/cypress/integration/createComponents.spec.js +++ /dev/null @@ -1,275 +0,0 @@ -import filterTests from "../support/filterTests" -const interact = require("../support/interact") - -filterTests(["all"], () => { - xcontext("Create Components", () => { - let headlineId - - before(() => { - cy.login() - cy.createTestApp() - cy.createTable("dog") - cy.addColumn("dog", "name", "Text") - cy.addColumn("dog", "age", "Number") - cy.addColumn("dog", "breed", "Options") - - cy.navigateToFrontend() - cy.wait(1000) //allow the iframe some wiggle room - }) - - //Use the tree to delete a selected component - const deleteSelectedComponent = () => { - cy.get( - ".nav-item.selected .actions > div > .icon" - ).click({ - force: true, - }) - cy.get(".spectrum-Popover.is-open li").contains("Delete").click() - cy.get(".spectrum-Modal button").contains("Delete Component").click({ - force: true, - }) - } - - it("should add a container", () => { - cy.searchAndAddComponent("Container").then(componentId => { - cy.getComponent(componentId).should("exist") - }) - }) - - it("should add a headline", () => { - cy.searchAndAddComponent("Headline").then(componentId => { - headlineId = componentId - cy.getComponent(headlineId).should("exist") - }) - }) - - it("should change the text of the headline", () => { - const text = "Lorem ipsum dolor sit amet." - cy.get("[data-cy=setting-text] input").type(text).blur() - cy.getComponent(headlineId).should("have.text", text) - }) - - it("should change the size of the headline", () => { - cy.get("[data-cy=setting-size]").scrollIntoView().click() - cy.get("[data-cy=setting-size]").within(() => { - cy.get(".spectrum-Form-item li.spectrum-Menu-item") - .contains("3XL") - .click() - }) - - cy.getComponent(headlineId).within(() => { - cy.get(".spectrum-Heading").should("have.css", "font-size", "60px") - }) - }) - - it("should create a form and reset to match schema", () => { - cy.searchAndAddComponent("Form").then(() => { - cy.get("[data-cy=setting-dataSource]").contains("Custom").click() - cy.get(interact.DROPDOWN).contains("dog").click() - cy.wait(500) - cy.searchAndAddComponent("Field Group").then(fieldGroupId => { - cy.contains("Update form fields").click() - cy.get(".spectrum-Modal") - .get(".confirm-wrap .spectrum-Button") - .click() - cy.wait(500) - cy.getComponent(fieldGroupId).within(() => { - cy.contains("name").should("exist") - cy.contains("age").should("exist") - cy.contains("breed").should("exist") - // cy.contains("image").should("exist") - }) - cy.getComponent(fieldGroupId).find("input").should("have.length", 2) - cy.getComponent(fieldGroupId) - .find(interact.SPECTRUM_PICKER) - .should("have.length", 1) - }) - }) - }) - - it("deletes a component", () => { - cy.searchAndAddComponent("Paragraph").then(componentId => { - cy.get("[data-cy=setting-_instanceName] input").type(componentId).blur() - cy.get( - ".nav-item.selected .actions > div > .icon" - ).click({ - force: true, - }) - cy.get(".spectrum-Popover.is-open li").contains("Delete").click() - cy.get(".spectrum-Modal button").contains("Delete Component").click({ - force: true, - }) - cy.getComponent(componentId).should("not.exist") - }) - }) - - it("should clear the iframe place holder when a form field has been set", () => { - cy.searchAndAddComponent("Form").then(formId => { - //For deletion - cy.get("[data-cy=setting-_instanceName] input") - .clear() - .type(formId) - .blur() - cy.get("[data-cy=setting-dataSource]").contains("Custom").click() - cy.get(".dropdown").contains("dog").click() - - const fieldTypeToColumnName = { - "Text Field": "name", - "Number Field": "age", - "Options Picker": "breed", - } - - const componentTypeLabels = Object.keys(fieldTypeToColumnName) - - const testFieldFocusOnCreate = componentLabel => { - cy.log("Adding: " + componentLabel) - return cy.searchAndAddComponent(componentLabel).then(componentId => { - cy.get("[data-cy=setting-field] button.spectrum-Picker").click() - - //Click the first appropriate field. They are filtered by type - cy.get( - "[data-cy=setting-field] .spectrum-Popover.is-open li.spectrum-Menu-item" - ) - .contains(fieldTypeToColumnName[componentLabel]) - .click() - cy.wait(500) - cy.getComponent(componentId) - .find(".component-placeholder") - .should("not.exist") - }) - } - - cy.wait(500) - cy.wrap(componentTypeLabels) - .each(label => { - return testFieldFocusOnCreate(label) - }) - .then(() => { - cy.get(".nav-item") - .contains(formId) - .click({ force: true }) - deleteSelectedComponent() - }) - }) - }) - - it("should populate the provider for charts with a data provider in its path", () => { - cy.searchAndAddComponent("Data Provider").then(providerId => { - //For deletion - cy.get("[data-cy=setting-_instanceName] input") - .clear() - .type(providerId) - .blur() - cy.get("[data-cy=setting-dataSource]") - .contains("Choose an option") - .click() - cy.get(`[data-cy=dataSource-popover-${providerId}] ul li`) - .contains("dog") - .click() - - const chartTypeLabels = [ - "Bar Chart", - "Line Chart", - "Area Chart", - "Pie Chart", - "Donut Chart", - "Candlestick Chart", - ] - - const testFocusOnCreate = chartLabel => { - cy.log("Adding: " + chartLabel) - cy.searchAndAddComponent(chartLabel).then(componentId => { - cy.get( - "[data-cy=dataProvider-prop-control] .spectrum-Picker" - ).should("not.have.class", "is-focused") - - // Pre populated. - cy.get("[data-cy=dataProvider-prop-control] .spectrum-Picker-label") - .contains(providerId) - .should("exist") - }) - } - cy.wait(1000) - cy.wrap(chartTypeLabels) - .each(label => { - return testFocusOnCreate(label) - }) - .then(() => { - cy.get(".nav-item") - .contains(providerId) - .click({ force: true }) - deleteSelectedComponent() - }) - }) - }) - - it("should replace the placeholder when a url is set on an image", () => { - cy.searchAndAddComponent("Image").then(imageId => { - cy.get("[data-cy=setting-_instanceName] input") - .clear() - .type(imageId) - .blur() - //return $("New Data Provider.Rows")[0]["Attachment"][0]["url"] - //No minio, so just enter something local that will not reslove - cy.get("[data-cy=url-prop-control] input[type=text]") - .type("cypress/fixtures/ghost.png") - .blur() - cy.getComponent(imageId) - .find(".component-placeholder") - .should("not.exist") - cy.getComponent(imageId).find(`img[alt=${imageId}]`).should("exist") - cy.get(".nav-item") - .contains(imageId) - .click({ force: true }) - deleteSelectedComponent() - }) - }) - - it("should add a markdown component.", () => { - cy.searchAndAddComponent("Markdown Viewer").then(markdownId => { - cy.get("[data-cy=setting-_instanceName] input") - .clear() - .type(markdownId) - .blur() - cy.get( - "[data-cy=value-prop-control] input[type=text].spectrum-Textfield-input" - ) - .type("# Hi") - .blur() - cy.getComponent(markdownId) - .find(".component-placeholder") - .should("not.exist") - cy.getComponent(markdownId) - .find(".editor-preview-full h1") - .contains("Hi") - cy.get(".nav-item") - .contains(markdownId) - .click({ force: true }) - deleteSelectedComponent() - }) - }) - - it("should direct the user when adding an Icon component.", () => { - cy.searchAndAddComponent("Icon").then(iconId => { - cy.get("[data-cy=setting-_instanceName] input") - .clear() - .type(iconId) - .blur() - cy.get("[data-cy=icon-prop-control] .spectrum-ActionButton").click() - cy.get("[data-cy=icon-popover].spectrum-Popover.is-open").within(() => { - cy.get(".search-input input").type("save").blur() - cy.get(".search-input button").click({ force: true }) - cy.get(".icon-area .icon-container").eq(0).click({ force: true }) - }) - cy.getComponent(iconId) - .find(".component-placeholder") - .should("not.exist") - cy.getComponent(iconId).find("i.ri-save-fill").should("exist") - cy.get(".nav-item") - .contains(iconId) - .click({ force: true }) - deleteSelectedComponent() - }) - }) - }) -}) diff --git a/packages/builder/cypress/integration/createScreen.spec.js b/packages/builder/cypress/integration/createScreen.spec.js deleted file mode 100644 index a516e279f4..0000000000 --- a/packages/builder/cypress/integration/createScreen.spec.js +++ /dev/null @@ -1,54 +0,0 @@ -import filterTests from "../support/filterTests" -const interact = require('../support/interact') - -filterTests(["smoke", "all"], () => { - context("Screen Tests", () => { - before(() => { - cy.login() - cy.createTestApp() - cy.navigateToFrontend() - }) - - it("Should successfully create a screen", () => { - cy.createScreen("test") - cy.get(interact.BODY).within(() => { - cy.contains("/test").should("exist") - }) - }) - - it("Should update the url", () => { - cy.createScreen("test with spaces") - cy.get(interact.BODY).within(() => { - cy.contains("/test-with-spaces").should("exist") - }) - }) - - it("should delete all screens then create first screen via button", () => { - cy.deleteAllScreens() - - cy.contains("Create first screen").click() - cy.get(interact.BODY, { timeout: 2000 }).should('contain', '/home') - }) - - it("Should create and filter screens by access level", () => { - const accessLevels = ["Basic", "Admin", "Public", "Power"] - - for (const access of accessLevels){ - // Create screen with specified access level - cy.createScreen(access, access) - // Filter by access level and confirm screen visible - cy.filterScreensAccessLevel(access) - cy.get(interact.BODY).within(() => { - cy.get(interact.NAV_ITEM).should('contain', access.toLowerCase()) - }) - } - - // Filter by All screens - Confirm all screens visible - cy.filterScreensAccessLevel("All screens") - cy.get(interact.BODY).should('contain', accessLevels[0]) - .and('contain', accessLevels[1]) - .and('contain', accessLevels[2]) - .and('contain', accessLevels[3]) - }) - }) -}) diff --git a/packages/builder/cypress/integration/createTable.spec.js b/packages/builder/cypress/integration/createTable.spec.js deleted file mode 100644 index 36d78afb29..0000000000 --- a/packages/builder/cypress/integration/createTable.spec.js +++ /dev/null @@ -1,112 +0,0 @@ -import filterTests from "../support/filterTests" -const interact = require('../support/interact') - -filterTests(["smoke", "all"], () => { - context("Create a Table", () => { - before(() => { - cy.login() - cy.createTestApp() - }) - - it("should create a new Table", () => { - cy.createTable("dog") - // Check if Table exists - cy.get(interact.TABLE_TITLE_H1, { timeout: 1000 }).should("have.text", "dog") - }) - - it("adds a new column to the table", () => { - cy.addColumn("dog", "name", "Text") - cy.contains("name").should("be.visible") - }) - - it("creates a row in the table", () => { - cy.addRow(["Rover"]) - cy.contains("Rover").should("be.visible") - }) - - it("updates a column on the table", () => { - cy.get(interact.TABLE_TITLE).click() - cy.get(interact.SPECTRUM_TABLE_EDIT).click() - cy.get(interact.MODAL_INNER_WRAPPER).within(() => { - - cy.get("input").eq(0).type("updated", { force: true }) - // Unset table display column - cy.get(interact.SPECTRUM_SWITCH_INPUT).eq(1).click() - cy.contains("Save Column").click() - }) - cy.contains("nameupdated ").should("contain", "nameupdated") - }) - - it("edits a row", () => { - cy.contains("button", "Edit").click({ force: true }) - cy.wait(500) - cy.get(interact.SPECTRUM_MODAL_INPUT).clear() - cy.get(interact.SPECTRUM_MODAL_INPUT).type("Updated") - cy.contains("Save").click() - cy.contains("Updated").should("have.text", "Updated") - }) - - it("deletes a row", () => { - cy.get(interact.SPECTRUM_CHECKBOX_INPUT).check({ force: true }) - cy.contains("Delete 1 row").click() - cy.get(interact.SPECTRUM_MODAL).contains("Delete").click() - cy.contains("RoverUpdated").should("not.exist") - }) - - if (Cypress.env("TEST_ENV")) { - // No Pagination in CI - Test env only for the next two tests - xit("Adds 15 rows and checks pagination", () => { - // 10 rows per page, 15 rows should create 2 pages within table - const totalRows = 16 - for (let i = 1; i < totalRows; i++) { - cy.addRow([i]) - } - cy.reload() - cy.get(interact.SPECTRUM_PAGINATION, { timeout: 2000 }).within(() => { - cy.get(interact.SPECTRUM_ACTION_BUTTON).eq(1).click() - }) - cy.get(interact.SPECTRUM_PAGINATION).within(() => { - cy.get(interact.SPECTRUM_BODY_SECOND).contains("Page 2") - }) - }) - - xit("Deletes rows and checks pagination", () => { - // Delete rows, removing second page from table - cy.get(interact.SPECTRUM_CHECKBOX_INPUT).check({ force: true }) - cy.get(interact.POPOVERS).within(() => { - cy.get(interact.SPECTRUM_BUTTON).click({ force: true }) - }) - cy.get(interact.SPECTRUM_DIALOG_GRID).contains("Delete").click({ force: true }) - - // Confirm table only has one page - cy.get(interact.SPECTRUM_PAGINATION, { timeout: 1000 }).within(() => { - cy.get(interact.SPECTRUM_ACTION_BUTTON).eq(1).should("not.be.enabled") - }) - }) - } - - it("deletes a column", () => { - const columnName = "nameupdated" - cy.get(interact.TABLE_TITLE).click() - cy.get(interact.SPECTRUM_TABLE_EDIT).click() - cy.contains("Delete").click() - cy.get(interact.DELETE_COLUMN_CONFIRM).type(columnName) - cy.contains("Delete Column").click() - cy.contains("nameupdated").should("not.exist") - }) - - it("deletes a table", () => { - cy.get(interact.NAV_ITEM) - .contains("dog") - .parents(interact.NAV_ITEM) - .first() - .within(() => { - cy.get(interact.ACTION_SPECTRUM_ICON).click({ force: true }) - }) - cy.get(interact.SPECTRUM_MENU_CHILD2).click() - cy.get(interact.DELETE_TABLE_CONFIRM).type("dog") - cy.contains("Delete Table").click() - cy.contains("dog").should("not.exist") - }) - }) -}) diff --git a/packages/builder/cypress/integration/createView.spec.js b/packages/builder/cypress/integration/createView.spec.js deleted file mode 100644 index 9adc486f70..0000000000 --- a/packages/builder/cypress/integration/createView.spec.js +++ /dev/null @@ -1,157 +0,0 @@ -import filterTests from "../support/filterTests" -const interact = require('../support/interact') - -filterTests(['smoke', 'all'], () => { - context("Create a View", () => { - before(() => { - cy.login() - cy.createTestApp() - cy.createTable("data") - cy.addColumn("data", "group", "Text") - cy.addColumn("data", "age", "Number") - cy.addColumn("data", "rating", "Number") - - // 6 Rows - cy.addRow(["Students", 25, 1]) - cy.addRow(["Students", 20, 3]) - cy.addRow(["Students", 18, 6]) - cy.addRow(["Students", 25, 2]) - cy.addRow(["Teachers", 49, 5]) - cy.addRow(["Teachers", 36, 3]) - }) - - it("creates a view", () => { - cy.contains("Create view").click() - cy.get(interact.MODAL_INNER_WRAPPER).within(() => { - cy.get("input").type("Test View") - cy.get("button").contains("Create View").click({ force: true }) - }) - cy.get(interact.TABLE_TITLE_H1).contains("Test View") - cy.get(interact.TITLE).then($headers => { - expect($headers).to.have.length(3) - const headers = Array.from($headers).map(header => - header.textContent.trim() - ) - expect(removeSpacing(headers)).to.deep.eq(["group", "age", "rating"]) - }) - }) - - it("filters the view by age over 10", () => { - cy.contains("Filter").click() - cy.contains("Add Filter").click() - - cy.get(interact.MODAL_INNER_WRAPPER).within(() => { - cy.get(interact.SPECTRUM_PICKER_LABEL).eq(0).click() - cy.contains("age").click({ force: true }) - - cy.get(interact.SPECTRUM_PICKER_LABEL).eq(1).click() - cy.contains("More Than").click({ force: true }) - - cy.get("input").type(18) - cy.contains("Save").click() - }) - - cy.get(interact.SPECTRUM_TABLE_ROW).get($values => { - expect($values).to.have.length(5) - }) - }) - - it("creates a stats calculation view based on age", () => { - cy.wait(1000) - cy.contains("Calculate").click() - cy.get(interact.MODAL_INNER_WRAPPER).within(() => { - cy.get(interact.SPECTRUM_PICKER_LABEL).eq(0).click() - cy.contains("Statistics").click() - - cy.get(interact.SPECTRUM_PICKER_LABEL).eq(1).click() - cy.contains("age").click({ force: true }) - - cy.get(interact.SPECTRUM_BUTTON).contains("Save").click({ force: true }) - }) - - cy.wait(1000) - cy.get(interact.TITLE).then($headers => { - expect($headers).to.have.length(7) - const headers = Array.from($headers).map(header => - header.textContent.trim() - ) - expect(removeSpacing(headers)).to.deep.eq([ - "field", - "sum", - "min", - "max", - "count", - "sumsqr", - "avg", - ]) - }) - cy.get(interact.SPECTRUM_TABLE_CELL).then($values => { - let values = Array.from($values).map(header => header.textContent.trim()) - expect(values).to.deep.eq(["age", "155", "20", "49", "5", "5347", "31"]) - }) - }) - - it("groups the view by group", () => { - cy.contains("Group by").click() - cy.get(interact.MODAL_INNER_WRAPPER).within(() => { - cy.get(interact.SPECTRUM_PICKER_LABEL).eq(0).click() - cy.contains("group").click() - cy.contains("Save").click() - }) - cy.wait(1000) - cy.contains("Students").should("be.visible") - cy.contains("Teachers").should("be.visible") - - cy.get(interact.SPECTRUM_TABLE_CELL).then($values => { - let values = Array.from($values).map(header => header.textContent.trim()) - expect(values).to.deep.eq([ - "Students", - "70", - "20", - "25", - "3", - "1650", - "23.333333333333332", - "Teachers", - "85", - "36", - "49", - "2", - "3697", - "42.5", - ]) - }) - }) - - it("renames a view", () => { - cy.contains(interact.NAV_ITEM, "Test View") - .find(".actions .icon.open-popover") - .click({ force: true }) - cy.get(interact.SPECTRUM_MENU_ITEM_LABEL).contains("Edit").click() - cy.get(interact.MODAL_INNER_WRAPPER).within(() => { - cy.get("input").type(" Updated") - cy.contains("Save").click() - }) - cy.wait(1000) - cy.contains("Test View Updated").should("be.visible") - }) - - it("deletes a view", () => { - cy.contains(interact.NAV_ITEM, "Test View Updated") - .find(".actions .icon.open-popover") - .click({ force: true }) - cy.contains("Delete").click() - cy.contains("Delete View").click() - cy.wait(500) - cy.contains("TestView Updated").should("not.exist") - }) - }) - - function removeSpacing(headers) { - let newHeaders = [] - for (let header of headers) { - newHeaders.push(header.replace(/\s\s+/g, " ")) - } - return newHeaders - } -}) diff --git a/packages/builder/cypress/integration/customThemingProperties.spec.js b/packages/builder/cypress/integration/customThemingProperties.spec.js deleted file mode 100644 index e9de0985d0..0000000000 --- a/packages/builder/cypress/integration/customThemingProperties.spec.js +++ /dev/null @@ -1,86 +0,0 @@ -import filterTests from "../support/filterTests" - -filterTests(['all'], () => { - xcontext("Custom Theming Properties", () => { - before(() => { - cy.login() - cy.createTestApp() - cy.navigateToFrontend() - }) - - /* Default Values: - Button roundness = Large - Accent colour = Blue 600 - Accent colour (hover) = Blue 500 - Navigation bar background colour = Gray 100 - Navigation bar text colour = Gray 800 */ - it("should reset the color property values", () => { - // Open Theme modal and change colours - cy.get(".spectrum-ActionButton-label").contains("Theme").click() - cy.get(".spectrum-Picker").contains("Large").click() - .parents() - .get(".spectrum-Menu-itemLabel").contains("None").click() - changeThemeColors() - // Reset colours - cy.get(".spectrum-Button-label").contains("Reset").click({force: true}) - // Check values have reset - checkThemeColorDefaults() - }) - - /* Button Roundness Values: - None = 0 - Small = 4px - Medium = 8px - Large = 16px */ - it("should test button roundness", () => { - const buttonRoundnessValues = ["0", "4px", "8px", "16px"] - // Add button, change roundness and confirm value - cy.addComponent("Button", null).then((componentId) => { - buttonRoundnessValues.forEach(function (item, index){ - cy.get(".spectrum-ActionButton-label").contains("Theme").click() - cy.get(".setting").contains("Button roundness").parent() - .get(".select-wrapper").click() - cy.get(".spectrum-Popover").find('li').eq(index).click() - cy.get(".spectrum-Button").contains("View changes").click({force: true}) - cy.reload() - cy.getComponent(componentId) - .parents(".svelte-xiqd1c").eq(0).should('have.attr', 'style').and('contains', `--buttonBorderRadius:${item}`) - }) - }) - }) - - const changeThemeColors = () => { - // Changes the theme colours - cy.get(".spectrum-FieldLabel").contains("Accent color") - .parent().find(".container.svelte-z3cm5a").click() - .find('[title="Red 400"]').click() - cy.get(".spectrum-FieldLabel").contains("Accent color (hover)") - .parent().find(".container.svelte-z3cm5a").click() - .find('[title="Orange 400"]').click() - cy.get(".spectrum-FieldLabel").contains("Navigation bar background color") - .parent().find(".container.svelte-z3cm5a").click() - .find('[title="Yellow 400"]').click() - cy.get(".spectrum-FieldLabel").contains("Navigation bar text color") - .parent().find(".container.svelte-z3cm5a").click() - .find('[title="Green 400"]').click() - } - - const checkThemeColorDefaults = () => { - cy.get(".spectrum-FieldLabel").contains("Accent color") - .parent().find(".container.svelte-z3cm5a").click() - .get('[title="Blue 600"]').children().find('[aria-label="Checkmark"]') - cy.get(".spectrum-Dialog-grid").click() - cy.get(".spectrum-FieldLabel").contains("Accent color (hover)") - .parent().find(".container.svelte-z3cm5a").click() - .get('[title="Blue 500"]').children().find('[aria-label="Checkmark"]') - cy.get(".spectrum-Dialog-grid").click() - cy.get(".spectrum-FieldLabel").contains("Navigation bar background color") - .parent().find(".container.svelte-z3cm5a").click() - .get('[title="Gray 100"]').children().find('[aria-label="Checkmark"]') - cy.get(".spectrum-Dialog-grid").click() - cy.get(".spectrum-FieldLabel").contains("Navigation bar text color") - .parent().find(".container.svelte-z3cm5a").click() - .get('[title="Gray 800"]').children().find('[aria-label="Checkmark"]') - } - }) -}) diff --git a/packages/builder/cypress/integration/datasources/datasourceWizard.spec.js b/packages/builder/cypress/integration/datasources/datasourceWizard.spec.js deleted file mode 100644 index 837a433951..0000000000 --- a/packages/builder/cypress/integration/datasources/datasourceWizard.spec.js +++ /dev/null @@ -1,42 +0,0 @@ -import filterTests from "../../support/filterTests" - -filterTests(['all'], () => { - xcontext("Datasource Wizard", () => { - if (Cypress.env("TEST_ENV")) { - before(() => { - cy.login() - cy.createTestApp() - }) - - it("should navigate in and out of a datasource via wizard", () => { - // Select PostgreSQL and add config (without fetch) - const datasource = "Oracle" - cy.selectExternalDatasource(datasource) - cy.addDatasourceConfig(datasource, true) - - // Navigate back within datasource wizard - cy.get(".spectrum-Dialog-grid").within(() => { - cy.get(".spectrum-Button").contains("Back").click({ force: true }) - }) - - // Select PostgreSQL datasource again - cy.get(".item-list", { timeout: 1000 }).contains(datasource).click() - cy.get(".spectrum-Dialog-grid").within(() => { - cy.get(".spectrum-Button").contains("Continue").click({ force: true }) - }) - - // Fetch tables after selection - // Previously entered config should not have been saved - // Config is back to default values - // Modal will close and provide 500 error - cy.intercept('**/datasources').as('datasourceConnection') - cy.get(".spectrum-Dialog-grid").within(() => { - cy.get(".spectrum-Button").contains("Save and fetch tables").click({ force: true }) - }) - cy.wait("@datasourceConnection") - cy.get("@datasourceConnection").its('response.body') - .should('have.property', 'status', 500) - }) - } - }) -}) diff --git a/packages/builder/cypress/integration/datasources/mySql.spec.js b/packages/builder/cypress/integration/datasources/mySql.spec.js deleted file mode 100644 index 33aa72f0bb..0000000000 --- a/packages/builder/cypress/integration/datasources/mySql.spec.js +++ /dev/null @@ -1,229 +0,0 @@ -import filterTests from "../../support/filterTests" - -filterTests(["all"], () => { - context("MySQL Datasource Testing", () => { - if (Cypress.env("TEST_ENV")) { - before(() => { - cy.login() - cy.createTestApp() - }) - const datasource = "MySQL" - const queryName = "Cypress Test Query" - const queryRename = "CT Query Rename" - - it("Should add MySQL datasource without configuration", () => { - // Select MySQL datasource - cy.selectExternalDatasource(datasource) - // Attempt to fetch tables without applying configuration - cy.intercept("**/datasources").as("datasource") - cy.get(".spectrum-Button") - .contains("Save and fetch tables") - .click({ force: true }) - cy.wait(500) - // Intercept Request after button click & apply assertions - cy.wait("@datasource") - cy.get("@datasource") - .its("response.body") - .should( - "have.property", - "message", - "connect ECONNREFUSED 127.0.0.1:3306" - ) - cy.get("@datasource") - .its("response.body") - .should("have.property", "status", 500) - cy.get(".spectrum-Button").contains("Skip table fetch").click({ force: true }) - }) - - it("should add MySQL datasource and fetch tables", () => { - // Add & configure MySQL datasource - cy.selectExternalDatasource(datasource) - cy.intercept("**/datasources").as("datasource") - cy.addDatasourceConfig(datasource) - // Check response from datasource after adding configuration - cy.wait("@datasource") - cy.get("@datasource").its("response.statusCode").should("eq", 200) - // Confirm fetch tables was successful - cy.get(".spectrum-Table") - .eq(0) - .find(".spectrum-Table-row") - .its("length") - .should("be.gt", 0) - }) - - it("should check table fetching error", () => { - // MySQL test datasource contains tables without primary keys - cy.get(".spectrum-InLineAlert") - .should("contain", "Error fetching tables") - .and("contain", "No primary key constraint found") - }) - - it("should define a One relationship type", () => { - // Select relationship type & configure - cy.get(".spectrum-Button") - .contains("Define relationship") - .click({ force: true }) - cy.get(".spectrum-Dialog-grid").within(() => { - cy.get(".spectrum-Picker").eq(0).click() - cy.get(".spectrum-Popover").contains("One").click() - cy.get(".spectrum-Picker").eq(1).click() - cy.get(".spectrum-Popover").contains("REGIONS").click() - cy.get(".spectrum-Picker").eq(2).click() - cy.get(".spectrum-Popover").contains("REGION_ID").click() - cy.get(".spectrum-Picker").eq(3).click() - cy.get(".spectrum-Popover").contains("COUNTRIES").click() - cy.get(".spectrum-Picker").eq(4).click() - cy.get(".spectrum-Popover").contains("REGION_ID").click() - }) - // Save relationship & reload page - cy.get(".spectrum-ButtonGroup").within(() => { - cy.get(".spectrum-Button").contains("Save").click({ force: true }) - }) - cy.reload() - - // Confirm table length & column name - cy.get(".spectrum-Table") - .eq(1) - .find(".spectrum-Table-row") - .its("length") - .should("eq", 1) - cy.get(".spectrum-Table-cell").should("contain", "COUNTRIES to REGIONS") - }) - - it("should define a Many relationship type", () => { - // Select relationship type & configure - cy.get(".spectrum-Button") - .contains("Define relationship") - .click({ force: true }) - cy.get(".spectrum-Dialog-grid").within(() => { - cy.get(".spectrum-Picker").eq(0).click() - cy.get(".spectrum-Popover").contains("Many").click() - cy.get(".spectrum-Picker").eq(1).click() - cy.get(".spectrum-Popover").contains("LOCATIONS").click() - cy.get(".spectrum-Picker").eq(2).click() - cy.get(".spectrum-Popover").contains("REGIONS").click() - cy.get(".spectrum-Picker").eq(3).click() - cy.get(".spectrum-Popover").contains("COUNTRIES").click() - cy.get(".spectrum-Picker").eq(4).click() - cy.get(".spectrum-Popover").contains("COUNTRY_ID").click() - cy.get(".spectrum-Picker").eq(5).click() - cy.get(".spectrum-Popover").contains("REGION_ID").click() - // Save relationship & reload page - cy.get(".spectrum-Button").contains("Save").click({ force: true }) - cy.reload() - cy.wait(1000) - }) - // Confirm table length & relationship name - cy.get(".spectrum-Table", { timeout: 1000 }) - .eq(1) - .find(".spectrum-Table-row") - .its("length") - .should("eq", 2) - cy.get(".spectrum-Table-cell").should( - "contain", - "LOCATIONS through COUNTRIES → REGIONS" - ) - }) - - it("should delete relationships", () => { - // Delete both relationships - cy.get(".spectrum-Table") - .eq(1) - .find(".spectrum-Table-row") - .its("length") - .then(len => { - for (let i = 0; i < len; i++) { - cy.get(".spectrum-Table") - .eq(1) - .within(() => { - cy.get(".spectrum-Table-cell").eq(0).click({ force: true }) - }) - cy.get(".spectrum-Dialog-grid", { timeout: 500 }).within(() => { - cy.get(".spectrum-Button") - .contains("Delete") - .click({ force: true }) - }) - cy.reload() - cy.wait(500) - } - // Confirm relationships no longer exist - cy.get(".spectrum-Body").should( - "contain", - "No relationships configured" - ) - }) - }) - - it("should add a query", () => { - // Add query - cy.get(".spectrum-Button").contains("Add query").click({ force: true }) - cy.get(".spectrum-Form-item") - .eq(0) - .within(() => { - cy.get("input").type(queryName) - }) - // Insert Query within Fields section - cy.get(".CodeMirror textarea") - .eq(0) - .type("SELECT * FROM books", { force: true }) - // Intercept query execution - cy.intercept("**/queries/preview").as("query") - cy.get(".spectrum-Button").contains("Run Query").click({ force: true }) - cy.wait(500) - cy.wait("@query") - // Assert against Status Code & Body - cy.get("@query").its("response.statusCode").should("eq", 200) - cy.get("@query").its("response.body").should("not.be.empty") - // Save query - cy.intercept("POST", "**/queries").as("saveQuery") - cy.get(".spectrum-Button").contains("Save Query").click({ force: true }) - cy.wait("@saveQuery") - cy.get("@saveQuery").its("response.statusCode").should("eq", 200) - cy.get(".nav-item").should("contain", queryName) - }) - - it("should duplicate a query", () => { - /// Get query nav item - QueryName - cy.get(".nav-item") - .contains(queryName) - .parent() - .within(() => { - cy.get(".spectrum-Icon").eq(1).click({ force: true }) - }) - // Select and confirm duplication - cy.get(".spectrum-Menu").contains("Duplicate").click() - cy.get(".nav-item").should("contain", queryName + " (1)") - }) - - it("should edit a query name", () => { - // Rename query - cy.get(".spectrum-Form-item") - .eq(0) - .within(() => { - cy.get("input").clear().type(queryRename) - }) - // Click on a nav item - cy.get(".nav-item").first().click() - // Confirm name change - cy.get(".nav-item").should("contain", queryRename) - }) - - it("should delete a query", () => { - // Get query nav item - QueryName - cy.get(".nav-item") - .contains(queryRename) - .parent() - .within(() => { - cy.get(".spectrum-Icon").eq(1).click({ force: true }) - }) - // Select Delete - cy.get(".spectrum-Menu").contains("Delete").click() - cy.get(".spectrum-Button") - .contains("Delete Query") - .click({ force: true }) - // Confirm deletion - cy.get(".nav-item", { timeout: 1000 }).should("not.contain", queryRename) - }) - } - }) -}) diff --git a/packages/builder/cypress/integration/datasources/oracle.spec.js b/packages/builder/cypress/integration/datasources/oracle.spec.js deleted file mode 100644 index ae1ca5cd75..0000000000 --- a/packages/builder/cypress/integration/datasources/oracle.spec.js +++ /dev/null @@ -1,229 +0,0 @@ -import filterTests from "../../support/filterTests" - -filterTests(["all"], () => { - xcontext("Oracle Datasource Testing", () => { - if (Cypress.env("TEST_ENV")) { - before(() => { - cy.login() - cy.createTestApp() - }) - const datasource = "Oracle" - const queryName = "Cypress Test Query" - const queryRename = "CT Query Rename" - - it("Should add Oracle datasource and skip table fetch", () => { - // Select Oracle datasource - cy.selectExternalDatasource(datasource) - // Skip table fetch - no config added - cy.get(".spectrum-Button") - .contains("Skip table fetch") - .click({ force: true }) - cy.wait(500) - // Confirm config contains localhost - cy.get(".spectrum-Textfield-input", { timeout: 500 }) - .eq(1) - .should("have.value", "localhost") - // Add another Oracle datasource, configure & skip table fetch - cy.selectExternalDatasource(datasource) - cy.addDatasourceConfig(datasource, true) - // Confirm config and no tables - cy.get(".spectrum-Textfield-input") - .eq(1) - .should("have.value", Cypress.env("oracle").HOST) - cy.get(".spectrum-Body").eq(2).should("contain", "No tables found.") - }) - - it("Should add Oracle datasource and fetch tables without configuration", () => { - // Select Oracle datasource - cy.selectExternalDatasource(datasource) - // Attempt to fetch tables without applying configuration - cy.intercept("**/datasources").as("datasource") - cy.get(".spectrum-Button") - .contains("Save and fetch tables") - .click({ force: true }) - // Intercept Request after button click & apply assertions - cy.wait("@datasource") - cy.get("@datasource") - .its("response.body") - .should("have.property", "status", 500) - cy.get(".spectrum-Button").contains("Skip table fetch").click({ force: true }) - }) - - xit("should add Oracle datasource and fetch tables", () => { - // Add & configure Oracle datasource - cy.selectExternalDatasource(datasource) - cy.intercept("**/datasources").as("datasource") - cy.addDatasourceConfig(datasource) - // Check response from datasource after adding configuration - cy.wait("@datasource") - cy.get("@datasource").its("response.statusCode").should("eq", 200) - // Confirm fetch tables was successful - cy.get(".spectrum-Table") - .eq(0) - .find(".spectrum-Table-row") - .its("length") - .should("be.gt", 0) - }) - - xit("should define a One relationship type", () => { - // Select relationship type & configure - cy.get(".spectrum-Button") - .contains("Define relationship") - .click({ force: true }) - cy.get(".spectrum-Dialog-grid").within(() => { - cy.get(".spectrum-Picker").eq(0).click() - cy.get(".spectrum-Popover").contains("One").click() - cy.get(".spectrum-Picker").eq(1).click() - cy.get(".spectrum-Popover").contains("REGIONS").click() - cy.get(".spectrum-Picker").eq(2).click() - cy.get(".spectrum-Popover").contains("REGION_ID").click() - cy.get(".spectrum-Picker").eq(3).click() - cy.get(".spectrum-Popover").contains("COUNTRIES").click() - cy.get(".spectrum-Picker").eq(4).click() - cy.get(".spectrum-Popover").contains("REGION_ID").click() - // Save relationship & reload page - cy.get(".spectrum-Button").contains("Save").click({ force: true }) - cy.reload() - }) - // Confirm table length & column name - cy.get(".spectrum-Table") - .eq(1) - .find(".spectrum-Table-row") - .its("length") - .should("eq", 1) - cy.get(".spectrum-Table-cell").should("contain", "COUNTRIES to REGIONS") - }) - - xit("should define a Many relationship type", () => { - // Select relationship type & configure - cy.get(".spectrum-Button") - .contains("Define relationship") - .click({ force: true }) - cy.get(".spectrum-Dialog-grid").within(() => { - cy.get(".spectrum-Picker").eq(0).click() - cy.get(".spectrum-Popover").contains("Many").click() - cy.get(".spectrum-Picker").eq(1).click() - cy.get(".spectrum-Popover").contains("LOCATIONS").click() - cy.get(".spectrum-Picker").eq(2).click() - cy.get(".spectrum-Popover").contains("REGIONS").click() - cy.get(".spectrum-Picker").eq(3).click() - cy.get(".spectrum-Popover").contains("COUNTRIES").click() - cy.get(".spectrum-Picker").eq(4).click() - cy.get(".spectrum-Popover").contains("COUNTRY_ID").click() - cy.get(".spectrum-Picker").eq(5).click() - cy.get(".spectrum-Popover").contains("REGION_ID").click() - // Save relationship & reload page - cy.get(".spectrum-Button").contains("Save").click({ force: true }) - cy.reload() - }) - // Confirm table length & relationship name - cy.get(".spectrum-Table") - .eq(1) - .find(".spectrum-Table-row") - .its("length") - .should("eq", 2) - cy.get(".spectrum-Table-cell").should( - "contain", - "LOCATIONS through COUNTRIES → REGIONS" - ) - }) - - xit("should delete relationships", () => { - // Delete both relationships - cy.get(".spectrum-Table") - .eq(1) - .find(".spectrum-Table-row") - .its("length") - .then(len => { - for (let i = 0; i < len; i++) { - cy.get(".spectrum-Table") - .eq(1) - .within(() => { - cy.get(".spectrum-Table-row").eq(0).click() - }) - cy.get(".spectrum-Dialog-grid", { timeout: 500 }).within(() => { - cy.get(".spectrum-Button") - .contains("Delete") - .click({ force: true }) - }) - cy.reload() - } - // Confirm relationships no longer exist - cy.get(".spectrum-Body").should( - "contain", - "No relationships configured" - ) - }) - }) - - xit("should add a query", () => { - // Add query - cy.get(".spectrum-Button").contains("Add query").click({ force: true }) - cy.get(".spectrum-Form-item") - .eq(0) - .within(() => { - cy.get("input").type(queryName) - }) - // Insert Query within Fields section - cy.get(".CodeMirror textarea") - .eq(0) - .type("SELECT * FROM JOBS", { force: true }) - // Intercept query execution - cy.intercept("**/queries/preview").as("query") - cy.get(".spectrum-Button").contains("Run Query").click({ force: true }) - cy.wait(500) - cy.wait("@query") - // Assert against Status Code & Body - cy.get("@query").its("response.statusCode").should("eq", 200) - cy.get("@query").its("response.body").should("not.be.empty") - // Save query - cy.get(".spectrum-Button").contains("Save Query").click({ force: true }) - cy.get(".nav-item").should("contain", queryName) - }) - - xit("should duplicate a query", () => { - // Get query nav item - cy.get(".nav-item") - .contains(queryName) - .parent() - .within(() => { - cy.get(".spectrum-Icon").eq(1).click({ force: true }) - }) - // Select and confirm duplication - cy.get(".spectrum-Menu").contains("Duplicate").click() - cy.get(".nav-item").should("contain", queryName + " (1)") - }) - - xit("should edit a query name", () => { - // Rename query - cy.get(".spectrum-Form-item") - .eq(0) - .within(() => { - cy.get("input").clear().type(queryRename) - }) - // Save query - cy.get(".spectrum-Button").contains("Save Query").click({ force: true }) - cy.get(".nav-item").should("contain", queryRename) - }) - - xit("should delete a query", () => { - // Get query nav item - QueryName - cy.get(".nav-item") - .contains(queryName) - .parent() - .within(() => { - cy.get(".spectrum-Icon").eq(1).click({ force: true }) - }) - - // Select Delete - cy.get(".spectrum-Menu").contains("Delete").click() - cy.get(".spectrum-Button") - .contains("Delete Query") - .click({ force: true }) - - // Confirm deletion - cy.get(".nav-item", { timeout: 1000 }).should("not.contain", queryName) - }) - } - }) -}) diff --git a/packages/builder/cypress/integration/datasources/postgreSql.spec.js b/packages/builder/cypress/integration/datasources/postgreSql.spec.js deleted file mode 100644 index 8ef574566e..0000000000 --- a/packages/builder/cypress/integration/datasources/postgreSql.spec.js +++ /dev/null @@ -1,283 +0,0 @@ -import filterTests from "../../support/filterTests" - -filterTests(["all"], () => { - context("PostgreSQL Datasource Testing", () => { - if (Cypress.env("TEST_ENV")) { - before(() => { - cy.login() - cy.createTestApp() - }) - const datasource = "PostgreSQL" - const queryName = "Cypress Test Query" - const queryRename = "CT Query Rename" - - xit("Should add PostgreSQL datasource without configuration", () => { - // Select PostgreSQL datasource - cy.selectExternalDatasource(datasource) - // Attempt to fetch tables without applying configuration - cy.intercept("**/datasources").as("datasource") - cy.get(".spectrum-Button") - .contains("Save and fetch tables") - .click({ force: true }) - // Intercept Request after button click & apply assertions - cy.wait("@datasource") - cy.get("@datasource") - .its("response.body") - .should("have.property", "status", 500) - cy.get(".spectrum-Button").contains("Skip table fetch").click({ force: true }) - }) - - it("should add PostgreSQL datasource and fetch tables", () => { - // Add & configure PostgreSQL datasource - cy.selectExternalDatasource(datasource) - cy.intercept("**/datasources").as("datasource") - cy.addDatasourceConfig(datasource) - // Check response from datasource after adding configuration - cy.wait("@datasource") - cy.get("@datasource").its("response.statusCode").should("eq", 200) - cy.wait(2000) - // Confirm fetch tables was successful - cy.get(".spectrum-Table") - .eq(0) - .find(".spectrum-Table-row") - .its("length") - .should("be.gt", 0) - }) - - it("should define a One relationship type", () => { - // Select relationship type & configure - cy.get(".spectrum-Button") - .contains("Define relationship") - .click({ force: true }) - cy.get(".spectrum-Dialog-grid").within(() => { - cy.get(".spectrum-Picker").eq(0).click() - cy.get(".spectrum-Popover").contains("One").click() - cy.get(".spectrum-Picker").eq(1).click() - cy.get(".spectrum-Popover").contains("REGIONS").click() - cy.get(".spectrum-Picker").eq(2).click() - cy.get(".spectrum-Popover").contains("REGION_ID").click() - cy.get(".spectrum-Picker").eq(3).click() - cy.get(".spectrum-Popover").contains("COUNTRIES").click() - cy.get(".spectrum-Picker").eq(4).click() - cy.get(".spectrum-Popover").contains("REGION_ID").click() - // Save relationship & reload page - cy.get(".spectrum-Button").contains("Save").click({ force: true }) - cy.reload() - }) - // Confirm table length & column name - cy.get(".spectrum-Table") - .eq(1) - .find(".spectrum-Table-row") - .its("length") - .should("eq", 1) - cy.get(".spectrum-Table-cell").should("contain", "COUNTRIES to REGIONS") - }) - - it("should define a Many relationship type", () => { - // Select relationship type & configure - cy.get(".spectrum-Button") - .contains("Define relationship") - .click({ force: true }) - cy.get(".spectrum-Dialog-grid").within(() => { - cy.get(".spectrum-Picker").eq(0).click() - cy.get(".spectrum-Popover").contains("Many").click() - cy.get(".spectrum-Picker").eq(1).click() - cy.get(".spectrum-Popover").contains("LOCATIONS").click() - cy.get(".spectrum-Picker").eq(2).click() - cy.get(".spectrum-Popover").contains("REGIONS").click() - cy.get(".spectrum-Picker").eq(3).click() - cy.get(".spectrum-Popover").contains("COUNTRIES").click() - cy.get(".spectrum-Picker").eq(4).click() - cy.get(".spectrum-Popover").contains("COUNTRY_ID").click() - cy.get(".spectrum-Picker").eq(5).click() - cy.get(".spectrum-Popover").contains("REGION_ID").click() - // Save relationship & reload page - cy.get(".spectrum-Button").contains("Save").click({ force: true }) - cy.reload() - }) - // Confirm table length & relationship name - cy.get(".spectrum-Table") - .eq(1) - .find(".spectrum-Table-row") - .its("length") - .should("eq", 2) - cy.get(".spectrum-Table-cell").should( - "contain", - "LOCATIONS through COUNTRIES → REGIONS" - ) - }) - - it("should delete a relationship", () => { - cy.get(".hierarchy-items-container").contains("PostgreSQL").click({ force: true }) - cy.reload() - // Delete one relationship - cy.get(".spectrum-Table") - .eq(1) - .within(() => { - cy.get(".spectrum-Table-cell").eq(0).click({ force: true }) - }) - cy.get(".spectrum-Dialog-grid", { timeout: 500 }).within(() => { - cy.get(".spectrum-Button").contains("Delete").click({ force: true }) - }) - cy.reload() - cy.wait(500) - // Confirm relationship was deleted - cy.get(".spectrum-Table") - .eq(1) - .find(".spectrum-Table-row") - .its("length") - .should("eq", 1) - }) - - it("should add a query", () => { - // Add query - cy.get(".spectrum-Button").contains("Add query").click({ force: true }) - cy.get(".spectrum-Form-item") - .eq(0) - .within(() => { - cy.get("input").type(queryName) - }) - // Insert Query within Fields section - cy.get(".CodeMirror textarea") - .eq(0) - .type("SELECT * FROM books", { force: true }) - // Intercept query execution - cy.intercept("**/queries/preview").as("query") - cy.get(".spectrum-Button").contains("Run Query").click({ force: true }) - cy.wait(500) - cy.wait("@query") - // Assert against Status Code & Body - cy.get("@query").its("response.statusCode").should("eq", 200) - cy.get("@query").its("response.body").should("not.be.empty") - // Save query - cy.intercept("**/queries").as("saveQuery") - cy.get(".spectrum-Button").contains("Save Query").click({ force: true }) - cy.wait("@saveQuery") - cy.get(".spectrum-Tabs-content", { timeout: 2000 }).should("contain", queryName) - }) - - it("should switch to schema with no tables", () => { - // Switch Schema - To one without any tables - cy.get(".hierarchy-items-container").contains("PostgreSQL").click({ force: true }) - switchSchema("randomText") - - // No tables displayed - cy.get(".spectrum-Body", { timeout: 10000 }).eq(2, { timeout: 10000 }).should("contain", "No tables found") - - // Previously created query should be visible - cy.get(".spectrum-Table").should("contain", queryName) - }) - - it("should switch schemas", () => { - // Switch schema - To one with tables - switchSchema("1") - - // Confirm tables exist - Check for specific one - cy.get(".spectrum-Table", { timeout: 20000 }).eq(0).should("contain", "test") - cy.get(".spectrum-Table") - .eq(0) - .find(".spectrum-Table-row") - .its("length") - .should("eq", 1) - - // Confirm specific table visible within left nav bar - cy.get(".hierarchy-items-container").should("contain", "test") - - // Switch back to public schema - switchSchema("public") - - // Confirm tables exist - again - cy.get(".spectrum-Table", { timeout: 20000 }).eq(0).should("contain", "REGIONS") - cy.get(".spectrum-Table") - .eq(0) - .find(".spectrum-Table-row") - .its("length") - .should("be.gt", 1) - - // Confirm specific table visible within left nav bar - cy.get(".hierarchy-items-container").should("contain", "REGIONS") - - // No relationships and one query - cy.get(".spectrum-Body") - .eq(3) - .should("contain", "No relationships configured.") - cy.get(".spectrum-Table").eq(1).should("contain", queryName) - }) - - it("should duplicate a query", () => { - // Locate previously created query - cy.get(".nav-item") - .contains(queryName) - .siblings(".actions") - .within(() => { - cy.get(".spectrum-Icon").click({ force: true }) - }) - // Select and confirm duplication - cy.get(".spectrum-Menu").contains("Duplicate").click() - cy.get(".nav-item").should("contain", queryName + " (1)") - }) - - it("should edit a query name", () => { - // Access query - cy.get(".hierarchy-items-container", { timeout: 2000 }) - //.contains(queryName + " (1)") - .contains(queryName) - .click({ force: true }) - - // Rename query - cy.wait(1000) - cy.get(".spectrum-Form-item", { timeout: 2000 }) - .eq(0) - .within(() => { - cy.get("input").clear().type(queryRename) - }) - - // Click on a nav item and confirm name change - cy.get(".nav-item").first().click() - // Confirm name change - cy.get(".nav-item").should("contain", queryRename) - }) - - it("should delete a query", () => { - // Get query nav item - QueryName - cy.get(".nav-item") - .contains(queryRename) - .parent() - .within(() => { - cy.get(".spectrum-Icon").eq(1).click({ force: true }) - }) - // Select Delete - cy.get(".spectrum-Menu").contains("Delete").click() - cy.get(".spectrum-Button") - .contains("Delete Query") - .click({ force: true }) - // Confirm deletion - cy.reload() - cy.get(".nav-item", { timeout: 30000 }).contains(datasource).click({ force: true }) - cy.get(".nav-item", { timeout: 1000 }).should("not.contain", queryRename) - }) - - const switchSchema = schema => { - // Edit configuration - Change Schema - cy.get(".spectrum-Textfield") - .eq(6) - .within(() => { - cy.get("input").clear().type(schema) - }) - // Save configuration & fetch - cy.get(".spectrum-Button").contains("Save").click({ force: true }) - cy.get(".spectrum-Button") - .contains("Fetch tables") - .click({ force: true }) - // Click fetch tables again within modal - cy.get(".spectrum-Dialog-grid").within(() => { - cy.get(".spectrum-Button") - .contains("Fetch tables") - .click({ force: true }) - }) - cy.reload() - cy.wait(1000) - } - } - }) -}) diff --git a/packages/builder/cypress/integration/datasources/rest.spec.js b/packages/builder/cypress/integration/datasources/rest.spec.js deleted file mode 100644 index ec9864a47d..0000000000 --- a/packages/builder/cypress/integration/datasources/rest.spec.js +++ /dev/null @@ -1,45 +0,0 @@ -import filterTests from "../../support/filterTests" - -filterTests(["smoke", "all"], () => { - context("REST Datasource Testing", () => { - before(() => { - cy.login() - cy.createTestApp() - }) - - const datasource = "REST" - const restUrl = "https://api.openbrewerydb.org/breweries" - - it("Should add REST datasource with incorrect API", () => { - // Select REST datasource - cy.selectExternalDatasource(datasource) - // Enter incorrect api & attempt to send query - cy.get(".query-buttons", { timeout: 1000 }).contains("Add query").click({ force: true }) - cy.intercept("**/preview").as("queryError") - cy.get("input").clear().type("random text") - cy.get(".spectrum-Button").contains("Send").click({ force: true }) - // Intercept Request after button click & apply assertions - cy.wait("@queryError") - cy.get("@queryError") - .its("response.body") - .should("have.property", "message", "Invalid URL: http://random text?") - cy.get("@queryError") - .its("response.body") - .should("have.property", "status", 400) - }) - - it("should add and configure a REST datasource", () => { - // Select REST datasource and create query - cy.selectExternalDatasource(datasource) - cy.wait(500) - // createRestQuery confirms query creation - cy.createRestQuery("GET", restUrl, "/breweries") - // Confirm status code response within REST datasource - cy.get(".stats", { timeout: 1000 }).within(() => { - cy.get(".spectrum-FieldLabel") - .eq(0) - .should("contain", 200) - }) - }) - }) -}) diff --git a/packages/builder/cypress/integration/queryLevelTransformers.spec.js b/packages/builder/cypress/integration/queryLevelTransformers.spec.js deleted file mode 100644 index 2b74e0c2e5..0000000000 --- a/packages/builder/cypress/integration/queryLevelTransformers.spec.js +++ /dev/null @@ -1,140 +0,0 @@ -import filterTests from "../support/filterTests" -const interact = require('../support/interact') - -filterTests(["smoke", "all"], () => { - context("Query Level Transformers", () => { - before(() => { - cy.login() - cy.createTestApp() - }) - - it("should write a transformer function", () => { - // Add REST datasource - contains API for breweries - const datasource = "REST" - const restUrl = "https://api.openbrewerydb.org/breweries" - cy.selectExternalDatasource(datasource) - cy.createRestQuery("GET", restUrl, "/breweries") - cy.get(interact.SPECTRUM_TABS_ITEM).contains("Transformer").click() - // Get Transformer Function from file - cy.readFile("cypress/support/queryLevelTransformerFunction.js").then( - transformerFunction => { - cy.get(interact.CODEMIRROR_TEXTAREA) - // Highlight current text and overwrite with file contents - .type(Cypress.platform === "darwin" ? "{cmd}a" : "{ctrl}a", { - force: true, - }) - .type(transformerFunction, { parseSpecialCharSequences: false }) - } - ) - // Send Query - cy.intercept("**/queries/preview").as("query") - cy.get(interact.SPECTRUM_BUTTON).contains("Send").click({ force: true }) - cy.wait("@query") - // Assert against Status Code, body, & body rows - cy.get("@query").its("response.statusCode").should("eq", 200) - cy.get("@query").its("response.body").should("not.be.empty") - cy.get("@query").its("response.body.rows").should("not.be.empty") - }) - - it("should add data to the previous query", () => { - // Add REST datasource - contains API for breweries - const datasource = "REST" - const restUrl = "https://api.openbrewerydb.org/breweries" - cy.selectExternalDatasource(datasource) - cy.createRestQuery("GET", restUrl, "/breweries") - cy.get(interact.SPECTRUM_TABS_ITEM).contains("Transformer").click() - // Get Transformer Function with Data from file - cy.readFile( - "cypress/support/queryLevelTransformerFunctionWithData.js" - ).then(transformerFunction => { - //console.log(transformerFunction[1]) - cy.get(interact.CODEMIRROR_TEXTAREA) - // Highlight current text and overwrite with file contents - .type(Cypress.platform === "darwin" ? "{cmd}a" : "{ctrl}a", { - force: true, - }) - .type(transformerFunction, { parseSpecialCharSequences: false }) - }) - // Send Query - cy.intercept("**/queries/preview").as("query") - cy.get(interact.SPECTRUM_BUTTON).contains("Send").click({ force: true }) - cy.wait("@query") - // Assert against Status Code, body, & body rows - cy.get("@query").its("response.statusCode").should("eq", 200) - cy.get("@query").its("response.body").should("not.be.empty") - cy.get("@query").its("response.body.rows").should("not.be.empty") - }) - - it("should run an invalid query within the transformer section", () => { - // Add REST datasource - contains API for breweries - const datasource = "REST" - const restUrl = "https://api.openbrewerydb.org/breweries" - cy.selectExternalDatasource(datasource) - cy.createRestQuery("GET", restUrl, "/breweries") - cy.get(interact.SPECTRUM_TABS_ITEM).contains("Transformer").click() - // Clear the code box and add "test" - cy.get(interact.CODEMIRROR_TEXTAREA) - .type(Cypress.platform === "darwin" ? "{cmd}a" : "{ctrl}a", { - force: true, - }) - .type("test") - // Run Query and intercept - cy.intercept("**/preview").as("queryError") - cy.get(interact.SPECTRUM_BUTTON).contains("Send").click({ force: true }) - cy.wait("@queryError") - cy.wait(500) - // Assert against message and status for the query error - cy.get("@queryError") - .its("response.body") - .should("have.property", "message", "test is not defined") - cy.get("@queryError") - .its("response.body") - .should("have.property", "status", 400) - }) - - xit("should run an invalid query via POST request", () => { - // POST request with transformer as null - cy.request({ - method: "POST", - url: `${Cypress.config().baseUrl}/api/queries/`, - body: { - fields: { headers: {}, queryString: null, path: null }, - parameters: [], - schema: {}, - name: "test", - queryVerb: "read", - transformer: null, - datasourceId: "test", - }, - // Expected 400 error - Transformer must be a string - failOnStatusCode: false, - }).then(response => { - expect(response.status).to.equal(400) - expect(response.body.message).to.include( - 'Invalid body - "transformer" must be a string' - ) - }) - }) - - xit("should run an empty query", () => { - // POST request with Transformer as an empty string - cy.request({ - method: "POST", - url: `${Cypress.config().baseUrl}/api/queries/preview`, - body: { - fields: { headers: {}, queryString: null, path: null }, - queryVerb: "read", - transformer: "", - datasourceId: "test", - }, - // Expected 400 error - Transformer is not allowed to be empty - failOnStatusCode: false, - }).then(response => { - expect(response.status).to.equal(400) - expect(response.body.message).to.include( - 'Invalid body - "transformer" is not allowed to be empty' - ) - }) - }) - }) -}) diff --git a/packages/builder/cypress/integration/renameAnApplication.spec.js b/packages/builder/cypress/integration/renameAnApplication.spec.js deleted file mode 100644 index 4460750b07..0000000000 --- a/packages/builder/cypress/integration/renameAnApplication.spec.js +++ /dev/null @@ -1,112 +0,0 @@ -import filterTests from "../support/filterTests" -const interact = require("../support/interact") - -filterTests(["all"], () => { - context("Rename an App", () => { - beforeEach(() => { - cy.login() - cy.createTestApp() - }) - - it("should rename an unpublished application", () => { - const appName = "Cypress Tests" - const appRename = "Cypress Renamed" - // Rename app, Search for app, Confirm name was changed - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 }) - renameApp(appName, appRename) - cy.reload() - cy.searchForApplication(appRename) - cy.get(interact.APP_TABLE).find(interact.TITLE).should("have.length", 1) - cy.applicationInAppTable(appRename) - // Set app name back to Cypress Tests - cy.reload() - renameApp(appRename, appName) - }) - - xit("Should rename a published application", () => { - // It is not possible to rename a published application - const appName = "Cypress Tests" - const appRename = "Cypress Renamed" - // Publish the app - cy.get(interact.TOP_RIGHT_NAV) - cy.get(interact.SPECTRUM_BUTTON) - .contains("Publish") - .click({ force: true }) - cy.get(interact.SPECTRUM_DIALOG_GRID).within(() => { - // Click publish again within the modal - cy.get(interact.SPECTRUM_BUTTON) - .contains("Publish") - .click({ force: true }) - }) - // Rename app, Search for app, Confirm name was changed - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 }) - renameApp(appName, appRename, true) - cy.get(interact.APP_TABLE).find(interact.WRAPPER).should("have.length", 1) - cy.applicationInAppTable(appRename) - }) - - it("Should try to rename an application to have no name", () => { - const appName = "Cypress Tests" - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 }) - renameApp(appName, " ", false, true) - // Close modal and confirm name has not been changed - cy.get(interact.SPECTRUM_DIALOG_GRID, { timeout: 1000 }).contains("Cancel").click() - cy.applicationInAppTable(appName) - }) - - xit("Should create two applications with the same name", () => { - // It is not possible to have applications with the same name - const appName = "Cypress Tests" - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 }) - cy.get(interact.SPECTRUM_BUTTON), { timeout: 500 } - .contains("Create app") - .click({ force: true }) - cy.contains(/Start from scratch/).click() - cy.get(interact.SPECTRUM_MODAL).within(() => { - cy.get("input").eq(0).type(appName) - cy.get(interact.SPECTRUM_BUTTON_GROUP) - .contains("Create app") - .click({ force: true }) - cy.get(interact.ERROR).should( - "have.text", - "Another app with the same name already exists" - ) - }) - }) - - it("should validate application names", () => { - // App name must be letters, numbers and spaces only - // This test checks numbers and special characters specifically - const appName = "Cypress Tests" - const numberName = 12345 - const specialCharName = "£$%^" - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 }) - renameApp(appName, numberName) - cy.applicationInAppTable(numberName) - renameApp(numberName, specialCharName) - cy.get(interact.ERROR).should( - "have.text", - "App name must be letters, numbers and spaces only" - ) - // Set app name back to Cypress Tests - renameApp(numberName, appName) - }) - - const renameApp = (originalName, changedName, published, noName) => { - cy.searchForApplication(originalName) - cy.get(interact.APP_TABLE, { timeout: 1000 }).within(() => { - cy.get(".app-row-actions button") - .contains("Manage") - .eq(0) - .click({ force: true }) - }) - cy.get(".spectrum-Tabs-item").contains("Settings").click() - cy.get(".spectrum-Tabs-item.is-selected").contains("Settings") - cy.get(".settings-tab").should("be.visible") - cy.get(".details-section .page-action button") - .contains("Edit") - .click({ force: true }) - cy.updateAppName(changedName, noName) - } - }) -}) diff --git a/packages/builder/cypress/integration/revertApp.spec.js b/packages/builder/cypress/integration/revertApp.spec.js deleted file mode 100644 index 0fb58e89e9..0000000000 --- a/packages/builder/cypress/integration/revertApp.spec.js +++ /dev/null @@ -1,75 +0,0 @@ -import filterTests from "../support/filterTests" -const interact = require('../support/interact') - -filterTests(['smoke', 'all'], () => { - context("Revert apps", () => { - before(() => { - cy.login() - cy.createTestApp() - }) - - it("should try to revert an unpublished app", () => { - // Click revert icon - cy.get(interact.TOP_RIGHT_NAV).within(() => { - cy.get(interact.AREA_LABEL_REVERT).click({ force: true }) - }) - cy.get(interact.SPECTRUM_MODAL).within(() => { - // Enter app name before revert - cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).type("Cypress Tests") - cy.intercept('**/revert').as('revertApp') - // Click Revert - cy.get(interact.SPECTRUM_BUTTON).contains("Revert").click({ force: true }) - // Intercept Request after button click & apply assertions - cy.wait("@revertApp") - cy.get("@revertApp").its('response.body').should('have.property', 'message', "App has not yet been deployed") - cy.get("@revertApp").its('response.body').should('have.property', 'status', 400) - }) - }) - - it("should revert a published app", () => { - cy.navigateToFrontend() - - // Add initial component - Paragraph - cy.searchAndAddComponent("Paragraph") - // Publish app - cy.get(interact.SPECTRUM_BUTTON).contains("Publish").click({ force: true }) - cy.get(interact.SPECTRUM_BUTTON_GROUP).within(() => { - cy.get(interact.SPECTRUM_BUTTON).contains("Publish").click({ force: true }) - }) - cy.wait(1000) // Wait for next modal to finish loading - cy.get(interact.SPECTRUM_BUTTON_GROUP, { timeout: 1000 }).within(() => { - cy.get(interact.SPECTRUM_BUTTON).contains("Done").click({ force: true }) - }) - - // Add second component - Button - cy.searchAndAddComponent("Button") - // Click Revert - cy.get(interact.TOP_RIGHT_NAV).within(() => { - cy.get(interact.AREA_LABEL_REVERT).click({ force: true }) - }) - cy.get(interact.SPECTRUM_DIALOG_GRID).within(() => { - cy.get("input").type("Cypress Tests") - // Click Revert - cy.get(interact.SPECTRUM_BUTTON).contains("Revert").click({ force: true }) - cy.wait(2000) // Wait for app to finish reverting - }) - // Confirm Paragraph component is still visible - cy.get(interact.ROOT, { timeout: 1000 }).contains("New Paragraph") - // Confirm Button component is not visible - cy.get(interact.ROOT, { timeout: 1000 }).should("not.have.text", "New Button") - }) - - it("should enter incorrect app name when reverting", () => { - // Click Revert - cy.get(interact.TOP_RIGHT_NAV, { timeout: 1000 }).within(() => { - cy.get(interact.AREA_LABEL_REVERT).click({ force: true }) - }) - // Enter incorrect app name - cy.get(interact.SPECTRUM_DIALOG_GRID).within(() => { - cy.get("input").type("Cypress Tests") - // Revert button within modal should be disabled - cy.get(interact.SPECTRUM_BUTTON).eq(1).should('be.disabled') - }) - }) - }) -}) diff --git a/packages/builder/cypress/plugins/index.js b/packages/builder/cypress/plugins/index.js deleted file mode 100644 index 771ba886b5..0000000000 --- a/packages/builder/cypress/plugins/index.js +++ /dev/null @@ -1,23 +0,0 @@ -/// -// *********************************************************** -// This example plugins/index.js can be used to load plugins -// -// You can change the location of this file or turn off loading -// the plugins file with the 'pluginsFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/plugins-guide -// *********************************************************** - -// This function is called when a project is opened or re-opened (e.g. due to -// the project's config changing) - -/** - * @type {Cypress.PluginConfig} - */ -// eslint-disable-next-line no-unused-vars -module.exports = (on, config) => { - // `on` is used to hook into various events Cypress emits - // `config` is the resolved Cypress config - require("cypress-terminal-report/src/installLogsPrinter")(on) -} diff --git a/packages/builder/cypress/support/commands.js b/packages/builder/cypress/support/commands.js deleted file mode 100644 index b1fa629023..0000000000 --- a/packages/builder/cypress/support/commands.js +++ /dev/null @@ -1,930 +0,0 @@ -Cypress.on("uncaught:exception", () => { - return false -}) - -// ACCOUNTS & USERS -Cypress.Commands.add("login", (email, password) => { - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 30000 }) - cy.url() - .should("include", "/builder/") - .then(url => { - if (url.includes("builder/admin")) { - // create admin user - cy.get("input").first().type("test@test.com") - cy.get('input[type="password"]').first().type("test") - cy.get('input[type="password"]').eq(1).type("test") - cy.contains("Create super admin user").click({ force: true }) - } - if (url.includes("builder/auth") || url.includes("builder/admin")) { - // login - cy.contains("Sign in to Budibase").then(() => { - if (email == null) { - cy.get("input").first().type("test@test.com") - cy.get('input[type="password"]').type("test") - } else { - cy.get("input").first().type(email) - cy.get('input[type="password"]').type(password) - } - cy.get("button").first().click({ force: true }) - cy.wait(1000) - }) - } - }) -}) - -Cypress.Commands.add("logOut", () => { - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 30000 }) - cy.get(".user-dropdown .avatar > .icon").click({ force: true }) - cy.get(".spectrum-Popover[data-cy='user-menu']").within(() => { - cy.get("li[data-cy='user-logout']").click({ force: true }) - }) - cy.wait(2000) -}) - -Cypress.Commands.add("logoutNoAppGrid", () => { - // Logs user out when app grid is not present - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 30000 }) - cy.get(".avatar > .icon").click({ force: true }) - cy.get(".spectrum-Popover[data-cy='user-menu']").within(() => { - cy.get(".spectrum-Menu-item").contains("Log out").click({ force: true }) - }) - cy.wait(2000) -}) - -Cypress.Commands.add("createUser", (email, permission) => { - cy.contains("Users").click() - cy.get(`[data-cy="add-user"]`).click() - cy.get(".spectrum-Dialog-grid").within(() => { - // Enter email - cy.get(".spectrum-Textfield-input").clear().click().type(email) - - // Select permission, if applicable - // Default is App User - if (permission != null) { - cy.get(".spectrum-Picker-label").click() - cy.get(".spectrum-Menu").within(() => { - cy.get(".spectrum-Menu-item") - .contains(permission) - .click({ force: true }) - }) - } - // Add user - cy.get(".spectrum-Button").contains("Add users").click({ force: true }) - cy.get(".spectrum-ActionButton").contains("Add email").should("not.exist") - }) - // Onboarding modal - cy.get(".spectrum-Dialog-grid", { timeout: 5000 }).contains( - "Choose your onboarding" - ) - cy.get(".spectrum-Dialog-grid").within(() => { - cy.get(".onboarding-type").eq(1).click() - cy.get(".spectrum-Button").contains("Done").click({ force: true }) - cy.get(".spectrum-Button").contains("Cancel").should("not.exist") - }) - - // Accounts created modal - Click Done button - cy.get(".spectrum-Button").contains("Done").click({ force: true }) -}) - -Cypress.Commands.add("deleteUser", email => { - // Assumes user has access to Users section - cy.contains("Users", { timeout: 2000 }).click() - cy.contains(email).click() - - cy.get(".title").within(() => { - cy.get(".spectrum-Icon").click({ force: true }) - }) - cy.get(".spectrum-Menu").within(() => { - cy.get(".spectrum-Menu-item").contains("Delete").click({ force: true }) - }) - cy.get(".spectrum-Dialog-grid").contains("Delete user").click({ force: true }) -}) - -Cypress.Commands.add("updateUserInformation", (firstName, lastName) => { - cy.get(".user-dropdown .avatar > .icon", { timeout: 2000 }).click({ - force: true, - }) - - cy.get(".spectrum-Popover[data-cy='user-menu']").within(() => { - cy.get("li[data-cy='user-info']").click({ force: true }) - }) - - cy.get(".spectrum-Modal.is-open").within(() => { - cy.get("[data-cy='user-first-name']").clear() - - if (!firstName || firstName == "") { - cy.get("[data-cy='user-first-name']").invoke("val").should("be.empty") - } else { - cy.get("[data-cy='user-first-name']") - .type(firstName) - .should("have.value", firstName) - .blur() - } - - cy.get("[data-cy='user-last-name']").clear() - - if (!lastName || lastName == "") { - cy.get("[data-cy='user-last-name']").invoke("val").should("be.empty") - } else { - cy.get("[data-cy='user-last-name']") - .type(lastName) - .should("have.value", lastName) - .blur() - } - cy.get(".confirm-wrap").within(() => { - cy.get("button").contains("Update information").click({ force: true }) - }) - cy.get(".spectrum-Dialog-grid").should("not.exist") - }) -}) - -Cypress.Commands.add("setUserRole", (user, role) => { - cy.contains("Users").click() - cy.contains(user).click() - - // Set Role - cy.wait(500) - cy.get(".spectrum-Form-itemField") - .eq(3) - .within(() => { - cy.get(".spectrum-Picker-label").click({ force: true }) - }) - cy.get(".spectrum-Menu").within(() => { - cy.get(".spectrum-Menu-itemLabel").contains(role).click({ force: true }) - }) - cy.get(".spectrum-Form-itemField").eq(3).should("contain", role) -}) - -// APPLICATIONS -Cypress.Commands.add("createTestApp", () => { - const appName = "Cypress Tests" - cy.deleteApp(appName) - cy.createApp(appName, "This app is used for Cypress testing.") -}) - -Cypress.Commands.add("createApp", (name, addDefaultTable) => { - const shouldCreateDefaultTable = - typeof addDefaultTable != "boolean" ? true : addDefaultTable - - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 30000 }) - cy.url({ timeout: 30000 }).should("include", "/apps") - cy.get(`[data-cy="create-app-btn"]`, { timeout: 5000 }).click({ force: true }) - - // If apps already exist - cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`, { - timeout: 5000, - }) - .its("body") - .then(val => { - if (val.length > 0) { - cy.get(`[data-cy="create-app-btn"]`, { timeout: 5000 }).click({ - force: true, - }) - } - }) - - cy.get(".spectrum-Modal").within(() => { - cy.get("input").eq(0).should("have.focus") - if (name && name != "") { - cy.get("input").eq(0).clear() - cy.get("input").eq(0).type(name).should("have.value", name).blur() - } - cy.get(".spectrum-ButtonGroup") - .contains("Create app") - .click({ force: true }) - cy.wait(2000) - }) - if (shouldCreateDefaultTable) { - cy.createTable("Cypress Tests", true) - } -}) - -Cypress.Commands.add("deleteApp", name => { - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 30000 }) - cy.wait(2000) - cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`) - .its("body") - .then(val => { - const findAppName = val.some(val => val.name == name) - if (findAppName) { - if (val.length > 0) { - const appId = val.reduce((acc, app) => { - if (name === app.name) { - acc = app.appId - } - return acc - }, "") - - if (appId == "") { - return - } - - // Go to app overview - const appIdParsed = appId.split("_").pop() - const actionEleId = `[data-cy=row_actions_${appIdParsed}]` - cy.get(actionEleId).within(() => { - cy.contains("Manage").click({ force: true }) - }) - cy.wait(500) - - // Unpublish first if needed - cy.get(`[data-cy="app-status"]`).then($status => { - if ($status.text().includes("- Unpublish")) { - // Exact match for Unpublish - cy.contains("Unpublish").click({ force: true }) - cy.get(".spectrum-Modal").within(() => { - cy.contains("Unpublish app").click({ force: true }) - }) - } - }) - - // Delete app - cy.get(".app-overview-actions-icon").within(() => { - cy.get(".spectrum-Icon").click({ force: true }) - }) - cy.get(".spectrum-Menu").contains("Delete").click({ force: true }) - cy.get(".spectrum-Dialog-grid").within(() => { - cy.get("input").type(name) - }) - cy.get(".spectrum-Button--warning").click() - } else { - return - } - } else { - return - } - }) -}) - -Cypress.Commands.add("deleteAllApps", () => { - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 30000 }) - cy.wait(500) - cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`, { - timeout: 5000, - }) - .its("body") - .then(val => { - for (let i = 0; i < val.length; i++) { - cy.deleteApp(val[i].name) - cy.reload() - } - }) -}) - -Cypress.Commands.add("unlockApp", unlock_config => { - let config = { ...unlock_config } - - cy.get(".spectrum-Modal .spectrum-Dialog[data-cy='app-lock-modal']") - .should("be.visible") - .within(() => { - if (config.owned) { - cy.get(".spectrum-Dialog-heading").contains("Locked by you") - cy.get(".lock-expiry-body").contains( - "This lock will expire in 10 minutes from now" - ) - - cy.intercept("**/lock").as("unlockApp") - cy.get(".spectrum-Button") - .contains("Release Lock") - .click({ force: true }) - cy.wait("@unlockApp") - cy.get("@unlockApp").its("response.statusCode").should("eq", 200) - cy.get("@unlockApp").its("response.body").should("deep.equal", { - message: "Lock released successfully.", - }) - } else { - //Show the name ? - cy.get(".lock-expiry-body").should("not.be.visible") - cy.get(".spectrum-Button").contains("Done") - } - }) -}) - -Cypress.Commands.add("updateAppName", (changedName, noName) => { - cy.get(".spectrum-Modal").within(() => { - if (noName == true) { - cy.get("input").clear() - cy.get(".spectrum-Dialog-grid") - .click() - .contains("App name must be letters, numbers and spaces only") - return cy - } - cy.get("input").clear() - cy.get("input") - .eq(0) - .type(changedName) - .should("have.value", changedName) - .blur() - cy.get(".spectrum-ButtonGroup").contains("Save").click({ force: true }) - cy.wait(500) - }) -}) - -Cypress.Commands.add("publishApp", resolvedAppPath => { - // Assumes you have navigated to an application first - cy.get(".toprightnav button.spectrum-Button") - .contains("Publish") - .click({ force: true }) - - cy.get(".spectrum-Modal [data-cy='deploy-app-modal']") - .should("be.visible") - .within(() => { - cy.get(".spectrum-Button").contains("Publish").click({ force: true }) - cy.wait(1000) - }) - - // Verify that the app url is presented correctly to the user - cy.get(".spectrum-Modal [data-cy='deploy-app-success-modal']") - .should("be.visible") - .within(() => { - let appUrl = Cypress.config().baseUrl + "/app/" + resolvedAppPath - cy.get("[data-cy='deployed-app-url'] input").should("have.value", appUrl) - cy.get(".spectrum-Button").contains("Done").click({ force: true }) - }) -}) - -Cypress.Commands.add("alterAppVersion", (appId, version) => { - return cy - .request("put", `${Cypress.config().baseUrl}/api/applications/${appId}`, { - version: version || "0.0.1-alpha.0", - }) - .then(resp => { - expect(resp.status).to.eq(200) - }) -}) - -Cypress.Commands.add("importApp", (exportFilePath, name) => { - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 30000 }) - - cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`) - .its("body") - .then(val => { - if (val.length > 0) { - cy.get(`[data-cy="create-app-btn"]`).click({ force: true }) - } - cy.wait(500) - cy.get(`[data-cy="import-app-btn"]`).click({ - force: true, - }) - }) - - cy.get(".spectrum-Modal").within(() => { - cy.get("input").eq(1).should("have.focus") - - cy.get(".spectrum-Dropzone").selectFile(exportFilePath, { - action: "drag-drop", - }) - - cy.get(".gallery .filename").contains("exported-app.txt") - - if (name && name != "") { - cy.get("input").eq(0).type(name).should("have.value", name).blur() - } - cy.get(".confirm-wrap button") - .should("not.be.disabled") - .click({ force: true }) - cy.wait(3000) - }) -}) - -// Filters visible with 1 or more -Cypress.Commands.add("searchForApplication", appName => { - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 30000 }) - cy.wait(2000) - - // No app filter functionality if only 1 app exists - cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`) - .its("body") - .then(val => { - if (val.length < 2) { - return - } else { - // Searches for the app - cy.get(".filter").then(() => { - cy.get(".spectrum-Textfield").within(() => { - cy.get("input").eq(0).clear({ force: true }) - cy.get("input").eq(0).type(appName, { force: true }) - }) - }) - } - }) -}) - -// Assumes there are no others -Cypress.Commands.add("applicationInAppTable", appName => { - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 30000 }) - cy.get(".appTable", { timeout: 5000 }).within(() => { - cy.get(".title").contains(appName).should("exist") - }) -}) - -Cypress.Commands.add("createAppFromScratch", appName => { - cy.get(`[data-cy="create-app-btn"]`) - .contains("Start from scratch") - .click({ force: true }) - cy.get(".spectrum-Modal").within(() => { - cy.get("input") - .eq(0) - .clear() - .type(appName) - .should("have.value", appName) - .blur() - cy.get(".spectrum-ButtonGroup").contains("Create app").click() - cy.wait(10000) - }) - cy.createTable("Cypress Tests", true) -}) - -// TABLES -Cypress.Commands.add("createTable", (tableName, initialTable) => { - // Creates an internal Budibase DB table - if (!initialTable) { - cy.navigateToDataSection() - cy.get(`[data-cy="new-table"]`, { timeout: 2000 }).click() - } - cy.wait(2000) - cy.get(".item", { timeout: 2000 }) - .contains("Budibase DB") - .click({ force: true }) - .then(() => { - cy.get(".spectrum-Button", { timeout: 2000 }) - .contains("Continue") - .click({ force: true }) - }) - cy.get(".spectrum-Modal").contains("Create Table", { timeout: 10000 }) - cy.get(".spectrum-Modal", { timeout: 2000 }).within(() => { - cy.get("input", { timeout: 2000 }).first().type(tableName).blur() - cy.get(".spectrum-ButtonGroup").contains("Create").click() - }) - // Ensure modal has closed and table is created - cy.get(".spectrum-Modal", { timeout: 2000 }).should("not.exist") - cy.get(".spectrum-Tabs-content", { timeout: 2000 }).should( - "contain", - tableName - ) -}) - -Cypress.Commands.add("createTestTableWithData", () => { - cy.createTable("dog") - cy.addColumn("dog", "name", "Text") - cy.addColumn("dog", "age", "Number") -}) - -Cypress.Commands.add( - "addColumn", - (tableName, columnName, type, multiOptions = null) => { - // Select Table - cy.selectTable(tableName) - cy.contains(".nav-item", tableName).click() - cy.contains("Create column").click() - - // Configure column - cy.get(".spectrum-Modal").within(() => { - cy.get("input").first().type(columnName).blur() - - // Unset table display column - cy.contains("display column").click({ force: true }) - cy.get(".spectrum-Picker-label").click() - cy.contains(type).click() - - // Add options for Multi-select Type - if (multiOptions !== null) { - cy.get(".spectrum-Textfield-input").eq(1).type(multiOptions) - } - - cy.contains("Save Column").click() - }) - } -) - -Cypress.Commands.add("addRow", values => { - cy.contains("Create row").click() - cy.get(".spectrum-Modal").within(() => { - for (let i = 0; i < values.length; i++) { - cy.get("input").eq(i).type(values[i]).blur() - } - cy.get(".spectrum-ButtonGroup").contains("Create").click() - }) -}) - -Cypress.Commands.add("addRowMultiValue", values => { - cy.contains("Create row").click() - cy.get(".spectrum-Modal").within(() => { - cy.get(".spectrum-Form-itemField") - .click() - .then(() => { - cy.get(".spectrum-Popover").within(() => { - for (let i = 0; i < values.length; i++) { - cy.get(".spectrum-Menu-item").eq(i).click() - } - }) - cy.get(".spectrum-Dialog-grid").click("top") - cy.get(".spectrum-ButtonGroup").contains("Create").click() - }) - }) -}) - -Cypress.Commands.add("selectTable", tableName => { - cy.expandBudibaseConnection() - cy.contains(".nav-item", tableName).click() -}) - -Cypress.Commands.add("addCustomSourceOptions", totalOptions => { - cy.get('[data-cy="customOptions-prop-control"]').within(() => { - cy.get(".spectrum-ActionButton-label").click({ force: true }) - }) - for (let i = 0; i < totalOptions; i++) { - // Add radio button options - cy.get(".spectrum-Button-label", { timeout: 1000 }) - .contains("Add Option") - .click({ force: true }) - .then(() => { - cy.get("[placeholder='Label']", { timeout: 500 }).eq(i).type(i) - cy.get("[placeholder='Value']").eq(i).type(i) - }) - } - // Save options - cy.get(".spectrum-Button").contains("Save").click({ force: true }) -}) - -// DESIGN SECTION -Cypress.Commands.add("searchAndAddComponent", component => { - // Open component menu - cy.get(".icon-side-nav").within(() => { - cy.get(".icon-side-nav-item").eq(1).click() - }) - cy.get(".add-component > .spectrum-Button") - .contains("Add component") - .click({ force: true }) - cy.get(".container", { timeout: 1000 }).within(() => { - cy.get(".title").should("contain", "Add component") - - // Search and add component - cy.get(".spectrum-Textfield-input").clear().type(component) - cy.get(".body").within(() => { - cy.get(".component") - .contains(new RegExp("^" + component + "$"), { timeout: 3000 }) - .click({ force: true }) - }) - }) - cy.wait(1000) - cy.location().then(loc => { - const params = loc.pathname.split("/") - const componentId = params[params.length - 1] - cy.getComponent(componentId, { timeout: 3000 }).should("exist") - return cy.wrap(componentId) - }) -}) - -Cypress.Commands.add("addComponent", (category, component) => { - if (category) { - cy.get(`[data-cy="category-${category}"]`, { timeout: 3000 }).click({ - force: true, - }) - } - cy.wait(500) - if (component) { - cy.get(`[data-cy="component-${component}"]`, { timeout: 3000 }).click({ - force: true, - }) - } - cy.wait(1000) - cy.location().then(loc => { - const params = loc.pathname.split("/") - const componentId = params[params.length - 1] - cy.getComponent(componentId, { timeout: 3000 }).should("exist") - return cy.wrap(componentId) - }) -}) - -Cypress.Commands.add("getComponent", componentId => { - return cy - .get("iframe") - .its("0.contentDocument") - .should("exist") - .its("body") - .should("not.be.undefined") - .then(cy.wrap) - .find(`[data-id='${componentId}']`) -}) - -Cypress.Commands.add("createScreen", (route, accessLevelLabel) => { - // Blank Screen - cy.contains("Design").click() - cy.get(".spectrum-Button").contains("Add screen").click({ force: true }) - cy.get(".spectrum-Modal").within(() => { - cy.get("[data-cy='blank-screen']").click() - cy.get(".spectrum-Button").contains("Continue").click({ force: true }) - }) - cy.wait(500) - cy.get(".spectrum-Dialog-grid", { timeout: 500 }).within(() => { - cy.get(".spectrum-Form-itemField").eq(0).type(route) - cy.get(".confirm-wrap").contains("Continue").click({ force: true }) - }) - - cy.get(".spectrum-Modal", { timeout: 1000 }).within(() => { - if (accessLevelLabel) { - cy.get(".spectrum-Picker-label").click() - cy.wait(500) - cy.contains(accessLevelLabel).click() - } - cy.get(".spectrum-Button").contains("Done").click({ force: true }) - }) -}) - -Cypress.Commands.add( - "createDatasourceScreen", - (datasourceNames, accessLevelLabel) => { - cy.contains("Design").click() - cy.get(".spectrum-Button").contains("Add screen").click({ force: true }) - cy.get(".spectrum-Dialog-grid").within(() => { - cy.get("[data-cy='autogenerated-screens']").click() - cy.intercept("**/api/datasources").as("autoScreens") - cy.get(".spectrum-Button").contains("Continue").click({ force: true }) - cy.wait("@autoScreens") - cy.wait(5000) - }) - cy.get("[data-cy='autogenerated-screens']").should("not.exist") - cy.get("[data-cy='data-source-modal']", { timeout: 10000 }).within(() => { - for (let i = 0; i < datasourceNames.length; i++) { - cy.get(".data-source-entry") - .contains(datasourceNames[i], { timeout: 20000 }) - .click({ force: true }) - // Ensure the check mark is visible - cy.get(".data-source-entry") - .contains(datasourceNames[i]) - .get(".data-source-check", { timeout: 20000 }) - .should("exist") - } - - cy.get(".spectrum-Button").contains("Confirm").click({ force: true }) - }) - - cy.get(".spectrum-Modal", { timeout: 10000 }).within(() => { - if (accessLevelLabel) { - cy.get(".spectrum-Picker-label", { timeout: 10000 }).click() - cy.contains(accessLevelLabel).click() - } - cy.get(".spectrum-Button").contains("Done").click({ force: true }) - }) - - cy.contains("Design").click() - } -) - -Cypress.Commands.add( - "createAutogeneratedScreens", - (screenNames, accessLevelLabel) => { - cy.navigateToAutogeneratedModal() - - for (let i = 0; i < screenNames.length; i++) { - cy.get(".data-source-entry").contains(screenNames[i]).click() - } - - cy.get(".spectrum-Modal").within(() => { - if (accessLevelLabel) { - cy.get(".spectrum-Picker-label").click() - cy.wait(500) - cy.contains(accessLevelLabel).click() - } - cy.get(".spectrum-Button").contains("Confirm").click({ force: true }) - cy.wait(4000) - }) - } -) - -Cypress.Commands.add("filterScreensAccessLevel", accessLevel => { - // Filters screens by access level dropdown - cy.get(".body").within(() => { - cy.get(".spectrum-Form-item").eq(1).click() - }) - cy.get(".spectrum-Menu").within(() => { - cy.contains(accessLevel).click() - }) -}) - -Cypress.Commands.add("deleteScreen", screen => { - // Navigates to Design section and deletes specified screen - cy.contains("Design").click() - cy.get(".body").within(() => { - cy.contains(screen) - .siblings(".actions") - .within(() => { - cy.get(".spectrum-Icon").click({ force: true }) - }) - }) - cy.get(".spectrum-Menu > .spectrum-Menu-item > .spectrum-Menu-itemLabel") - .contains("Delete") - .click() - - cy.get( - ".spectrum-Dialog-grid > .spectrum-ButtonGroup > .confirm-wrap > .spectrum-Button" - ).click({ force: true }) - cy.get(".spectrum-Dialog-grid", { timeout: 10000 }).should("not.exist") -}) - -Cypress.Commands.add("deleteAllScreens", () => { - // Deletes all screens - cy.get(".body") - .find(".nav-item") - .its("length") - .then(len => { - for (let i = 0; i < len; i++) { - cy.get(".body > .nav-item") - .eq(0) - .invoke("text") - .then(text => { - cy.deleteScreen(text.trim()) - }) - } - }) -}) - -// NAVIGATION -Cypress.Commands.add("navigateToFrontend", () => { - // Clicks on Design tab and then the Home nav item - cy.wait(500) - cy.intercept("**/preview").as("preview") - cy.contains("Design").click() - cy.wait("@preview") - cy.get("@preview").then(res => { - if (res.statusCode != 200) { - cy.reload() - } - }) - cy.get(".spectrum-Search", { timeout: 20000 }).type("/") - cy.get(".nav-item", { timeout: 2000 }).contains("home").click({ force: true }) -}) - -Cypress.Commands.add("navigateToDataSection", () => { - // Clicks on the Data tab - cy.wait(500) - cy.contains("Data").click() -}) - -Cypress.Commands.add("navigateToAutogeneratedModal", () => { - // Screen name must already exist within datasource - cy.contains("Design").click() - cy.get(".spectrum-Button").contains("Add screen").click({ force: true }) - cy.get(".spectrum-Modal").within(() => { - cy.get(".item", { timeout: 2000 }) - .contains("Autogenerated screens") - .click({ force: true }) - cy.get(".spectrum-Button").contains("Continue").click({ force: true }) - cy.wait(500) - }) -}) - -// DATASOURCES -Cypress.Commands.add("selectExternalDatasource", datasourceName => { - // Navigates to Data Section - cy.navigateToDataSection() - // Open Datasource modal - cy.get(".nav").within(() => { - cy.get(".add-button").click() - }) - // Clicks specified datasource & continue - cy.get(".item-list", { timeout: 1000 }).contains(datasourceName).click() - cy.get(".spectrum-Dialog-grid").within(() => { - cy.get(".spectrum-Button").contains("Continue").click({ force: true }) - }) - cy.wait(500) -}) - -Cypress.Commands.add("addDatasourceConfig", (datasource, skipFetch) => { - // selectExternalDatasource should be called prior to this - // Adds the config for specified datasource & fetches tables - // Currently supports MySQL, PostgreSQL, Oracle - // Host IP Address - cy.get(".spectrum-Dialog-grid", { timeout: 500 }).within(() => { - cy.get(".form-row") - .eq(0) - .within(() => { - cy.get(".spectrum-Textfield").within(() => { - if (datasource == "Oracle") { - cy.get("input").clear().type(Cypress.env("oracle").HOST) - } else { - cy.get("input") - .clear({ force: true }) - .type(Cypress.env("HOST_IP"), { force: true }) - } - }) - }) - }) - // Database Name - cy.get(".spectrum-Dialog-grid").within(() => { - if (datasource == "MySQL") { - cy.get(".form-row") - .eq(4) - .within(() => { - cy.get("input").clear().type(Cypress.env("mysql").DATABASE) - }) - } else { - cy.get(".form-row") - .eq(2) - .within(() => { - if (datasource == "PostgreSQL") { - cy.get("input").clear().type(Cypress.env("postgresql").DATABASE) - } - if (datasource == "Oracle") { - cy.get("input").clear().type(Cypress.env("oracle").DATABASE) - } - }) - } - }) - // User - cy.get(".spectrum-Dialog-grid").within(() => { - if (datasource == "MySQL") { - cy.get(".form-row") - .eq(2) - .within(() => { - cy.get("input").clear().type(Cypress.env("mysql").USER) - }) - } else { - cy.get(".form-row") - .eq(3) - .within(() => { - if (datasource == "PostgreSQL") { - cy.get("input").clear().type(Cypress.env("postgresql").USER) - } - if (datasource == "Oracle") { - cy.get("input").clear().type(Cypress.env("oracle").USER) - } - }) - } - }) - // Password - cy.get(".spectrum-Dialog-grid").within(() => { - if (datasource == "MySQL") { - cy.get(".form-row") - .eq(3) - .within(() => { - cy.get("input").clear().type(Cypress.env("mysql").PASSWORD) - }) - } else { - cy.get(".form-row") - .eq(4) - .within(() => { - if (datasource == "PostgreSQL") { - cy.get("input").clear().type(Cypress.env("postgresql").PASSWORD) - } - if (datasource == "Oracle") { - cy.get("input").clear().type(Cypress.env("oracle").PASSWORD) - } - }) - } - }) - // Click to fetch tables - if (skipFetch) { - cy.get(".spectrum-Dialog-grid").within(() => { - cy.get(".spectrum-Button") - .contains("Skip table fetch") - .click({ force: true }) - }) - } else { - cy.intercept("**/tables").as("datasourceTables") - cy.get(".spectrum-Dialog-grid").within(() => { - cy.get(".spectrum-Button") - .contains("Save and fetch tables") - .click({ force: true }) - }) - // Wait for tables to be fetched - cy.wait("@datasourceTables", { timeout: 60000 }) - } -}) - -Cypress.Commands.add("createRestQuery", (method, restUrl, queryPrettyName) => { - // addExternalDatasource should be called prior to this - // Configures REST datasource & sends query - cy.get(".spectrum-Button", { timeout: 1000 }) - .contains("Add query") - .click({ force: true }) - // Select Method & add Rest URL - cy.get(".spectrum-Picker-label").eq(1).click() - cy.get(".spectrum-Menu").contains(method).click() - cy.get("input").clear().type(restUrl) - // Send query - cy.get(".spectrum-Button").contains("Send").click({ force: true }) - cy.get(".spectrum-Button", { timeout: 500 }) - .contains("Save") - .click({ force: true }) - cy.get(".hierarchy-items-container") - .should("contain", method) - .and("contain", queryPrettyName) -}) - -// MISC -Cypress.Commands.add("closeModal", () => { - cy.get(".spectrum-Modal", { timeout: 2000 }).within(() => { - cy.get(".close-icon").click() - }) - // Confirm modal has closed - cy.get(".spectrum-Modal", { timeout: 10000 }).should("not.exist") -}) - -Cypress.Commands.add("expandBudibaseConnection", () => { - if (Cypress.$(".nav-item > .content > .opened").length === 0) { - // expand the Budibase DB connection string - cy.get(".icon.arrow").eq(0).click() - } -}) diff --git a/packages/builder/cypress/support/cookies.js b/packages/builder/cypress/support/cookies.js deleted file mode 100644 index 3e2fba6481..0000000000 --- a/packages/builder/cypress/support/cookies.js +++ /dev/null @@ -1,3 +0,0 @@ -Cypress.Cookies.defaults({ - preserve: "budibase:auth", -}) diff --git a/packages/builder/cypress/support/filterTests.js b/packages/builder/cypress/support/filterTests.js deleted file mode 100644 index 074fd05d33..0000000000 --- a/packages/builder/cypress/support/filterTests.js +++ /dev/null @@ -1,16 +0,0 @@ -const filterTests = (testTags, runTest) => { - // testTags is an array of tags - // runTest is all tests - if (Cypress.env("TEST_TAGS")) { - const tags = Cypress.env("TEST_TAGS").split("/") - const found = testTags.some($testTags => tags.includes($testTags)) - - if (found) { - runTest() - } - } else { - runTest() - } -} - -export default filterTests diff --git a/packages/builder/cypress/support/index.js b/packages/builder/cypress/support/index.js deleted file mode 100644 index acd53a1592..0000000000 --- a/packages/builder/cypress/support/index.js +++ /dev/null @@ -1,22 +0,0 @@ -// *********************************************************** -// This example support/index.js is processed and -// loaded automatically before your test files. -// -// This is a great place to put global configuration and -// behavior that modifies Cypress. -// -// You can change the location of this file or turn off -// automatically serving support files with the -// 'supportFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/configuration -// *********************************************************** - -// Import commands.js using ES2015 syntax: -import "./commands" -import "./cookies" - -// Alternatively you can use CommonJS syntax: -// require('./commands') -require("cypress-terminal-report/src/installLogsCollector")() diff --git a/packages/builder/cypress/support/interact.js b/packages/builder/cypress/support/interact.js deleted file mode 100644 index 4f2451ce4b..0000000000 --- a/packages/builder/cypress/support/interact.js +++ /dev/null @@ -1,136 +0,0 @@ -// createApp test -export const CREATE_APP_BUTTON = '[data-cy="create-app-btn"]' -export const TEMPLATE_CATEGORY_FILTER = ".template-category-filters" -export const TEMPLATE_CATEGORY = ".template-categories" -export const APP_TABLE = ".appTable" -export const SPECTRUM_BUTTON_TEMPLATE = ".spectrum-Button" -export const TEMPLATE_CATEGORY_ACTIONGROUP = ".template-category" -export const TEMPLATE_CATEGORY_FILTER_ACTIONBUTTON = - ".template-category-filters .spectrum-ActionButton" -export const SPECTRUM_MODAL = ".spectrum-Modal" -export const APP_NAME_INPUT = "input" // we need to update this with atribute cy-data; -export const SPECTRUM_BUTTON_GROUP = ".spectrum-ButtonGroup" -export const SPECTRUM_MODAL_INPUT = ".spectrum-Modal input" - -//AddMultiOptionDatatype -export const CATEGORY_DATA = '[data-cy="category-Data"]' -export const COMPONENT_DATA_PROVIDER = '[data-cy="component-Data Provider"]' -export const DATASOURCE_PROP_CONTROL = '[data-cy="dataSource-prop-control"]' -export const DROPDOWN = ".dropdown" -export const SPECTRUM_PICKER_LABEL = ".spectrum-Picker-label" -export const DATASOURCE_FIELD_CONTROL = '[data-cy="field-prop-control"]' -export const OPTION_TYPE_PROP_CONTROL = '[data-cy="optionsType-prop-control' - -//AddRadioButtons -export const SPECTRUM_POPOVER = ".spectrum-Popover" -export const OPTION_SOURCE_PROP_CONROL = '[data-cy="optionsSource-prop-control' -export const APP_TABLE_STATUS = ".appTable .app-status" -export const APP_TABLE_ROW_ACTION = ".appTable .app-row-actions" -export const APP_TABLE_APP_NAME = '[data-cy="app-name-link"]' -export const DEPLOYMENT_TOP_NAV_GLOBESTRIKE = - ".deployment-top-nav svg[aria-label=GlobeStrike]" -export const DEPLOYMENT_TOP_GLOBE = ".deployment-top-nav svg[aria-label=Globe]" -export const PUBLISH_POPOVER_MENU = '[data-cy="publish-popover-menu"]' -export const PUBLISH_POPOVER_ACTION = '[data-cy="publish-popover-action"]' -export const PUBLISH_POPOVER_MESSAGE = ".publish-popover-message" -export const SPECTRUM_BUTTON = ".spectrum-Button" -export const SPECTRUM_LINK = ".spectrum-Link" -export const TOPRIGHTNAV_BUTTON_SPECTRUM = ".toprightnav button.spectrum-Button" - -//createComponents -export const SETTINGS = "[data-cy=Settings]" -export const SETTINGS_INPUT = "[data-cy=setting-text] input" -export const DESIGN = "[data-cy=Design]" -export const FONT_SIZE_PROP_CONTROL = "[data-cy=font-size-prop-control]" -export const DATA_CY_DATASOURCE = "[data-cy=setting-dataSource]" -export const DROPDOWN_CONTAINER = ".dropdown-container" -export const SPECTRUM_PICKER = ".spectrum-Picker" - -//autoScreens -export const LABEL_ADD_CIRCLE = "[aria-label=AddCircle]" -export const ITEM_DISABLED = ".item.disabled" -export const CONFIRM_WRAP_SPE_BUTTON = ".confirm-wrap .spectrum-Button" -export const DATA_SOURCE_ENTRY = ".data-source-entry" -export const BODY = ".body" - -//publishWorkFlow -export const DEPLOY_APP_MODAL = ".spectrum-Modal [data-cy=deploy-app-modal]" -export const DEPLOY_SUCCESS_MODAL = - ".spectrum-Modal [data-cy=deploy-app-success-modal]" -export const DEPLOY_APP_URL_INPUT = "[data-cy=deployed-app-url] input" -export const GLOBESTRIKE = "svg[aria-label=GlobeStrike]" -export const GLOBE = "svg[aria-label=Globe]" -export const UNPUBLISH_MODAL = "[data-cy=unpublish-modal]" -export const CONFIRM_WRAP_BUTTON = ".confirm-wrap button" -export const DEPLOYMENT_TOP_NAV = ".deployment-top-nav" - -//changeAppiconAndColour -export const APP_ROW_ACTION = ".app-row-actions-icon" -export const SPECTRUM_MENU = ".spectrum-Menu" -export const ICON_ITEM = ".icon-item" -export const FILL = ".fill" -export const COLOURSS = ".colors" -export const AREA_LABEL = "[aria-label]" -export const TITLE = ".title" -export const GRID = ".grid" -export const COLOUR = ".color" - -//createAutomation -export const ADD_BUTTON_SPECTRUM = ".add-button .spectrum-Icon" -export const MODAL_INNER_WRAPPER = ".modal-inner-wrapper" -export const SPECTRUM_BUTTON_CTA = ".spectrum-Button--cta" -export const SPECTRUM_TEXTFIELD_INPUT = ".spectrum-Textfield-input" - -//createTable -export const TABLE_TITLE_H1 = ".table-title h1" -export const TABLE_TITLE = ".title" -export const SPECTRUM_TABLE_EDIT = ".spectrum-Table-editIcon > use" -export const SPECTRUM_SWITCH_INPUT = ".spectrum-Switch-input" -export const SPECTRUM_CHECKBOX_INPUT = ".spectrum-Checkbox-input" -export const SPECTRUM_PAGINATION = ".spectrum-Pagination" -export const SPECTRUM_ACTION_BUTTON = ".spectrum-ActionButton" -export const SPECTRUM_BODY_SECOND = ".spectrum-Body--secondary" -export const POPOVERS = ".popovers" -export const SPECTRUM_DIALOG_GRID = ".spectrum-Dialog-grid" -export const DELETE_COLUMN_CONFIRM = '[data-cy="delete-column-confirm"]' -export const NAV_ITEM = ".nav-item" -export const ACTION_SPECTRUM_ICON = ".actions .spectrum-Icon" -export const SPECTRUM_MENU_CHILD2 = ".spectrum-Menu > :nth-child(2)" -export const DELETE_TABLE_CONFIRM = '[data-cy="delete-table-confirm"]' - -//adminAndManagement Folder -export const SPECTRUM_TABLE = ".spectrum-Table" -export const SPECTRUM_SIDENAV = ".spectrum-SideNav" -export const SPECTRUM_TABLE_ROW = ".spectrum-Table-row" -export const SPECTRUM_TABLE_CELL = ".spectrum-Table-cell" -export const FIELD = ".field" -export const CONTAINER = ".container" -export const REGENERATE = ".regenerate" -export const SPECTRUM_DIALOG_CONTENT = ".spectrum-Dialog-content" -export const SPECTRUM_ICON = ".spectrum-Icon" -export const SPECTRUM_HEADING = ".spectrum-Heading" -export const SPECTRUM_FORM_ITEMFIELD = ".spectrum-Form-itemField" -export const LIST_ITEMS = ".list-items" - -//createView -export const SPECTRUM_MENU_ITEM_LABEL = ".spectrum-Menu-itemLabel" - -//revertApp -export const TOP_RIGHT_NAV = ".toprightnav" -export const AREA_LABEL_REVERT = "[aria-label=Revert]" -export const ROOT = ".root" - -//queryLevelTransformers -export const SPECTRUM_TABS_ITEM = ".spectrum-Tabs-itemLabel" -export const CODEMIRROR_TEXTAREA = ".CodeMirror textarea" - -//renameApplication -export const WRAPPER = ".wrapper" -export const ERROR = ".error" -export const AREA_LABEL_MORE = "[aria-label=More]" -export const APP_ROW_ACTION_MENU_POPOVER = - '[data-cy="app-row-actions-menu-popover"]' -export const SPECTRUM_MENU_ITEM = ".spectrum-Menu-item" - -//commands -export const HOME_LOGO = ".home-logo" diff --git a/packages/builder/cypress/support/queryLevelTransformerFunction.js b/packages/builder/cypress/support/queryLevelTransformerFunction.js deleted file mode 100644 index 7dc05018f8..0000000000 --- a/packages/builder/cypress/support/queryLevelTransformerFunction.js +++ /dev/null @@ -1,14 +0,0 @@ -/* eslint-disable */ -const breweries = data -const totals = {} - -for (let brewery of breweries) - {const state = brewery.state - if (totals[state] == null) - {totals[state] = 1 - } else - {totals[state]++ - } -} -const entries = Object.entries(totals) -return entries.map(([state, count]) => ({ state, count })) diff --git a/packages/builder/cypress/support/queryLevelTransformerFunctionWithData.js b/packages/builder/cypress/support/queryLevelTransformerFunctionWithData.js deleted file mode 100644 index fcf50b4412..0000000000 --- a/packages/builder/cypress/support/queryLevelTransformerFunctionWithData.js +++ /dev/null @@ -1,31 +0,0 @@ -/* eslint-disable */ -const breweries = data -const totals = {} -for (let brewery of breweries) - {const state = brewery.state - if (totals[state] == null) - {totals[state] = 1 - } else - {totals[state]++ - } -} -const stateCodes = - {texas: "tx", - colorado: "co", - florida: "fl", - iwoa: "ia", - louisiana: "la", - california: "ca", - pennsylvania: "pa", - georgia: "ga", - "new hampshire": "nh", - virginia: "va", - michigan: "mi", - maryland: "md", - ohio: "oh", -} -const entries = Object.entries(totals) -return entries.map(([state, count]) => - {stateCodes[state.toLowerCase()] - return { state, count, flag: "http://flags.ox3.in/svg/us/${stateCode}.svg" } -}) diff --git a/packages/builder/cypress/ts/setup.ts b/packages/builder/cypress/ts/setup.ts deleted file mode 100644 index b6b12bf730..0000000000 --- a/packages/builder/cypress/ts/setup.ts +++ /dev/null @@ -1,4 +0,0 @@ -// @ts-ignore -import { run } from "../setup" - -run("../../server/src/index", "../../worker/src/index") diff --git a/packages/builder/package.json b/packages/builder/package.json index 84242e4e46..7def4ba371 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "2.0.22", + "version": "2.3.16", "license": "GPL-3.0", "private": true, "scripts": { @@ -9,20 +9,7 @@ "dev:builder": "routify -c dev:vite", "dev:vite": "vite --host 0.0.0.0", "rollup": "rollup -c -w", - "test": "jest", - "cy:setup": "ts-node ./cypress/ts/setup.ts", - "cy:setup:ci": "node ./cypress/setup.js", - "cy:open": "cypress open", - "cy:run": "cypress run", - "cy:run:ci": "cypress run --headed --browser chrome --spec cypress/integration/createApp.spec.js", - "cy:run:ci:record": "xvfb-run cypress run --headed --browser chrome --record", - "cy:test": "start-server-and-test cy:setup http://localhost:4100/builder cy:run", - "cy:ci": "start-server-and-test cy:setup:ci http://localhost:4100/builder cy:run:ci", - "cy:ci:record": "start-server-and-test cy:setup:ci http://localhost:4100/builder cy:run:ci:record; npm run cy:ci:report", - "cy:ci:report": "mochawesome-merge cypress/reports/*.json > cypress/reports/testReport.json && marge cypress/reports/testReport.json --reportDir cypress/reports --inline", - "cy:ci:notify": "node scripts/cypressResultsWebhook", - "cy:debug": "start-server-and-test cy:setup http://localhost:4100/builder cy:open", - "cy:debug:ci": "start-server-and-test cy:setup:ci http://localhost:4100/builder cy:open" + "test": "jest" }, "jest": { "globals": { @@ -71,23 +58,27 @@ } }, "dependencies": { - "@budibase/bbui": "^2.0.22", - "@budibase/client": "^2.0.22", - "@budibase/frontend-core": "^2.0.22", - "@budibase/string-templates": "^2.0.22", + "@budibase/bbui": "^2.3.16", + "@budibase/client": "^2.3.16", + "@budibase/frontend-core": "^2.3.16", + "@budibase/string-templates": "^2.3.16", + "@fortawesome/fontawesome-svg-core": "^6.2.1", + "@fortawesome/free-brands-svg-icons": "^6.2.1", + "@fortawesome/free-solid-svg-icons": "^6.2.1", "@sentry/browser": "5.19.1", + "@spectrum-css/accordion": "^3.0.24", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", "codemirror": "^5.59.0", "dayjs": "^1.11.2", "downloadjs": "1.4.7", "lodash": "4.17.21", - "posthog-js": "1.4.5", + "posthog-js": "^1.36.0", "remixicon": "2.5.0", "shortid": "2.2.15", "svelte-dnd-action": "^0.9.8", "svelte-loading-spinners": "^0.1.1", - "svelte-portal": "0.1.0", + "svelte-portal": "1.0.0", "uuid": "8.3.1", "yup": "0.29.2" }, @@ -117,9 +108,9 @@ "start-server-and-test": "^1.12.1", "svelte": "^3.48.0", "svelte-jester": "^1.3.2", - "ts-node": "^10.4.0", + "ts-node": "10.8.1", "tsconfig-paths": "4.0.0", - "typescript": "^4.5.5", + "typescript": "4.7.3", "vite": "^3.0.8" }, "gitHead": "115189f72a850bfb52b65ec61d932531bf327072" diff --git a/packages/builder/cypress/setup.js b/packages/builder/setup.js similarity index 79% rename from packages/builder/cypress/setup.js rename to packages/builder/setup.js index 0e2f25b028..744c20896a 100644 --- a/packages/builder/cypress/setup.js +++ b/packages/builder/setup.js @@ -1,14 +1,14 @@ -const cypressConfig = require("../cypress.json") +const testConfig = require("./testConfig.json") // normal development system -const SERVER_PORT = cypressConfig.env.PORT -const WORKER_PORT = cypressConfig.env.WORKER_PORT +const SERVER_PORT = testConfig.env.PORT +const WORKER_PORT = testConfig.env.WORKER_PORT if (!process.env.NODE_ENV) { process.env.NODE_ENV = "cypress" } process.env.ENABLE_ANALYTICS = "0" -process.env.JWT_SECRET = cypressConfig.env.JWT_SECRET +process.env.JWT_SECRET = testConfig.env.JWT_SECRET process.env.SELF_HOSTED = 1 process.env.WORKER_URL = `http://localhost:${WORKER_PORT}/` process.env.APPS_URL = `http://localhost:${SERVER_PORT}/` @@ -23,10 +23,7 @@ process.env.ALLOW_DEV_AUTOMATIONS = 1 // Stop info logs polluting test outputs process.env.LOG_LEVEL = "error" -exports.run = ( - serverLoc = "../../server/dist", - workerLoc = "../../worker/dist" -) => { +exports.run = (serverLoc = "../server/dist", workerLoc = "../worker/dist") => { // require("dotenv").config({ path: resolve(dir, ".env") }) // don't make this a variable or top level require // it will cause environment module to be loaded prematurely diff --git a/packages/builder/src/App.svelte b/packages/builder/src/App.svelte index 4d193df104..f31f45bb84 100644 --- a/packages/builder/src/App.svelte +++ b/packages/builder/src/App.svelte @@ -3,7 +3,6 @@ import { routes } from "../.routify/routes" import { NotificationDisplay, BannerDisplay } from "@budibase/bbui" import { parse, stringify } from "qs" - import HelpIcon from "components/common/HelpIcon.svelte" import LicensingOverlays from "components/portal/licensing/LicensingOverlays.svelte" const queryHandler = { parse, stringify } @@ -11,14 +10,10 @@