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..675707453e 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-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/.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/.vscode/settings.json b/.vscode/settings.json index 4838a4fd89..71f0092a59 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,7 +8,7 @@ "editor.defaultFormatter": "vscode.json-language-features" }, "[javascript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "vscode.typescript-language-features" }, "debug.javascript.terminalOptions": { "skipFiles": [ @@ -16,4 +16,7 @@ "/**" ] }, + "[typescript]": { + "editor.defaultFormatter": "vscode.typescript-language-features" + }, } 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/app-service-deployment.yaml b/charts/budibase/templates/app-service-deployment.yaml index 42fb35b5e4..01cd20f605 100644 --- a/charts/budibase/templates/app-service-deployment.yaml +++ b/charts/budibase/templates/app-service-deployment.yaml @@ -4,9 +4,6 @@ metadata: annotations: kompose.cmd: kompose convert kompose.version: 1.21.0 (992df58d8) -{{ if .Values.globals.logAnnotations }} -{{ toYaml .Values.globals.logAnnotations | indent 4 }} -{{ end }} creationTimestamp: null labels: io.kompose.service: app-service @@ -23,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 @@ -67,6 +67,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,13 +79,19 @@ 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 | quote }} - name: APPS_BUCKET_NAME value: {{ .Values.services.objectStore.appsBucketName | quote }} - - name: GLOBAL_CLOUD_BUCKET_NAME + - name: GLOBAL_BUCKET_NAME value: {{ .Values.services.objectStore.globalBucketName | quote }} - name: BACKUPS_BUCKET_NAME value: {{ .Values.services.objectStore.backupsBucketName | quote }} @@ -159,6 +167,18 @@ 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 }} diff --git a/charts/budibase/templates/proxy-service-deployment.yaml b/charts/budibase/templates/proxy-service-deployment.yaml index 3cde7a2388..6064905c4c 100644 --- a/charts/budibase/templates/proxy-service-deployment.yaml +++ b/charts/budibase/templates/proxy-service-deployment.yaml @@ -4,9 +4,6 @@ metadata: annotations: kompose.cmd: kompose convert kompose.version: 1.21.0 (992df58d8) -{{ if .Values.globals.logAnnotations }} -{{ toYaml .Values.globals.logAnnotations | indent 4 }} -{{ end }} creationTimestamp: null labels: app.kubernetes.io/name: budibase-proxy @@ -23,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/worker-service-deployment.yaml b/charts/budibase/templates/worker-service-deployment.yaml index 6324256780..ff56fb5019 100644 --- a/charts/budibase/templates/worker-service-deployment.yaml +++ b/charts/budibase/templates/worker-service-deployment.yaml @@ -4,9 +4,6 @@ metadata: annotations: kompose.cmd: kompose convert kompose.version: 1.21.0 (992df58d8) -{{ if .Values.globals.logAnnotations }} -{{ toYaml .Values.globals.logAnnotations | indent 4 }} -{{ end }} creationTimestamp: null labels: io.kompose.service: worker-service @@ -24,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 @@ -68,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: @@ -80,11 +82,17 @@ 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 | quote }} - name: APPS_BUCKET_NAME value: {{ .Values.services.objectStore.appsBucketName | quote }} - - name: GLOBAL_CLOUD_BUCKET_NAME + - name: GLOBAL_BUCKET_NAME value: {{ .Values.services.objectStore.globalBucketName | quote }} - name: BACKUPS_BUCKET_NAME value: {{ .Values.services.objectStore.backupsBucketName | quote }} @@ -150,6 +158,18 @@ 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 }} diff --git a/charts/budibase/values.yaml b/charts/budibase/values.yaml index 05d2d5e276..889d7e9e23 100644 --- a/charts/budibase/values.yaml +++ b/charts/budibase/values.yaml @@ -22,12 +22,6 @@ serviceAccount: podAnnotations: {} -# logAnnotations: -# co.elastic.logs/multiline.type: pattern -# co.elastic.logs/multiline.pattern: '^[[:space:]]' -# co.elastic.logs/multiline.negate: false -# co.elastic.logs/multiline.match: after - podSecurityContext: {} # fsGroup: 2000 @@ -112,6 +106,9 @@ globals: # elasticApmEnabled: # elasticApmSecretToken: # elasticApmServerUrl: +# globalAgentHttpProxy: +# globalAgentHttpsProxy: +# globalAgentNoProxy: services: budibaseVersion: latest @@ -121,7 +118,16 @@ services: 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 @@ -129,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 @@ -164,6 +179,7 @@ services: resources: {} objectStore: + # Set to false if using another object store such as S3 minio: true browser: true port: 9000 @@ -179,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/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..1626d520b6 100644 --- a/hosting/docker-compose.dev.yaml +++ b/hosting/docker-compose.dev.yaml @@ -25,9 +25,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,6 +36,8 @@ services: - couchdb-service extra_hosts: - "host.docker.internal:host-gateway" + environment: + - PROXY_ADDRESS=host.docker.internal couchdb-service: # platform: linux/amd64 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.hbs b/hosting/nginx.dev.conf similarity index 83% rename from hosting/nginx.dev.conf.hbs rename to hosting/nginx.dev.conf index 93a07435e5..1ecee422cd 100644 --- a/hosting/nginx.dev.conf.hbs +++ b/hosting/nginx.dev.conf @@ -25,17 +25,17 @@ http { } upstream app-service { - server {{address}}:4001; + server ${PROXY_ADDRESS}:4001; keepalive 32; } upstream worker-service { - server {{address}}:4002; + server ${PROXY_ADDRESS}:4002; keepalive 32; } upstream builder { - server {{address}}:3000; + server ${PROXY_ADDRESS}:3000; keepalive 32; } @@ -186,6 +186,26 @@ http { 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; diff --git a/hosting/proxy/Dockerfile b/hosting/proxy/Dockerfile index 68e8134750..42327be087 100644 --- a/hosting/proxy/Dockerfile +++ b/hosting/proxy/Dockerfile @@ -4,7 +4,7 @@ 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 @@ -16,4 +16,11 @@ 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 84% rename from hosting/nginx.prod.conf.hbs rename to hosting/proxy/nginx.prod.conf index 6f0f1b420d..fc2f51370b 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; @@ -76,27 +76,23 @@ 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; @@ -107,19 +103,17 @@ http { 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/(system|admin|global)/ { proxy_set_header Host $host; - - proxy_pass http://$worker:4003; + proxy_pass $worker; } location /worker/ { proxy_set_header Host $host; - - proxy_pass http://$worker:4003; + proxy_pass $worker; rewrite ^/worker/(.*)$ /$1 break; } @@ -138,7 +132,7 @@ http { 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_pass $apps; } location /api/ { @@ -157,7 +151,7 @@ http { 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/ { @@ -177,11 +171,11 @@ http { 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; } @@ -191,7 +185,7 @@ 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 / { @@ -205,7 +199,27 @@ http { 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/single/nginx/nginx-default-site.conf b/hosting/single/nginx/nginx-default-site.conf index acadb06250..3903c0647d 100644 --- a/hosting/single/nginx/nginx-default-site.conf +++ b/hosting/single/nginx/nginx-default-site.conf @@ -95,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/runner.sh b/hosting/single/runner.sh index ea825131db..6eebba62b6 100644 --- a/hosting/single/runner.sh +++ b/hosting/single/runner.sh @@ -78,7 +78,7 @@ mkdir -p ${DATA_DIR}/search chown -R couchdb:couchdb ${DATA_DIR}/couch redis-server --requirepass $REDIS_PASSWORD > /dev/stdout 2>&1 & /opt/clouseau/bin/clouseau > /dev/stdout 2>&1 & -/minio/minio server ${DATA_DIR}/minio > /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 diff --git a/lerna.json b/lerna.json index 818467f623..ffa2eb77f0 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.1.43-alpha.15", + "version": "2.2.12-alpha.4", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/package.json b/package.json index 6c147698ad..7da8db506d 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "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", @@ -44,8 +45,8 @@ "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}\"", @@ -55,15 +56,11 @@ "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 .", diff --git a/packages/backend-core/__mocks__/aws-sdk.ts b/packages/backend-core/__mocks__/aws-sdk.ts index 7fac80faa9..b8d91dbaa9 100644 --- a/packages/backend-core/__mocks__/aws-sdk.ts +++ b/packages/backend-core/__mocks__/aws-sdk.ts @@ -3,7 +3,10 @@ const mockS3 = { deleteObject: jest.fn().mockReturnThis(), deleteObjects: jest.fn().mockReturnThis(), createBucket: jest.fn().mockReturnThis(), - listObjects: 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(), } diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index db74a11144..bf667374b4 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/backend-core", - "version": "2.1.43-alpha.15", + "version": "2.2.12-alpha.4", "description": "Budibase backend core libraries used in server and worker", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", @@ -15,14 +15,18 @@ "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 --coverage --maxWorkers=2", "test:watch": "jest --watchAll" }, "dependencies": { - "@budibase/types": "2.1.43-alpha.15", + "@budibase/nano": "10.1.1", + "@budibase/types": "2.2.12-alpha.4", "@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", @@ -31,11 +35,10 @@ "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", - "nano": "^10.1.0", "node-fetch": "2.6.7", "passport-google-oauth": "2.0.0", "passport-jwt": "4.0.0", 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/src/context/mainContext.ts b/packages/backend-core/src/context/mainContext.ts index d743d2f49b..c44ec4e767 100644 --- a/packages/backend-core/src/context/mainContext.ts +++ b/packages/backend-core/src/context/mainContext.ts @@ -2,7 +2,7 @@ // store an app ID to pretend there is a context import env from "../environment" import Context from "./Context" -import { getDevelopmentAppID, getProdAppID } from "../db/conversions" +import * as conversions from "../db/conversions" import { getDB } from "../db/db" import { DocumentType, @@ -181,6 +181,14 @@ export function getAppId(): string | undefined { } } +export const getProdAppId = () => { + const appId = getAppId() + if (!appId) { + throw new Error("Could not get appId") + } + return conversions.getProdAppID(appId) +} + export function updateTenantId(tenantId?: string) { let context: ContextMap = updateContext({ tenantId, @@ -229,7 +237,7 @@ export function getProdAppDB(opts?: any): Database { if (!appId) { throw new Error("Unable to retrieve prod DB - no app ID.") } - return getDB(getProdAppID(appId), opts) + return getDB(conversions.getProdAppID(appId), opts) } /** @@ -241,5 +249,5 @@ export function getDevAppDB(opts?: any): Database { if (!appId) { throw new Error("Unable to retrieve dev DB - no app ID.") } - return getDB(getDevelopmentAppID(appId), opts) + return getDB(conversions.getDevelopmentAppID(appId), opts) } diff --git a/packages/backend-core/src/db/couch/DatabaseImpl.ts b/packages/backend-core/src/db/couch/DatabaseImpl.ts index de06b4e8ee..9b4761d961 100644 --- a/packages/backend-core/src/db/couch/DatabaseImpl.ts +++ b/packages/backend-core/src/db/couch/DatabaseImpl.ts @@ -1,4 +1,4 @@ -import Nano from "nano" +import Nano from "@budibase/nano" import { AllDocsResponse, AnyDocument, diff --git a/packages/backend-core/src/db/utils.ts b/packages/backend-core/src/db/utils.ts index 590c3eeef8..5e501c8d22 100644 --- a/packages/backend-core/src/db/utils.ts +++ b/packages/backend-core/src/db/utils.ts @@ -14,7 +14,7 @@ import { doWithDB, allDbs, directCouchAllDbs } from "./db" import { getAppMetadata } from "../cache/appMetadata" import { isDevApp, isDevAppID, getProdAppID } from "./conversions" import * as events from "../events" -import { App, Database, ConfigType } from "@budibase/types" +import { App, Database, ConfigType, isSettingsConfig } from "@budibase/types" /** * Generates a new app ID. @@ -489,18 +489,12 @@ export const getScopedFullConfig = async function ( // custom logic for settings doc if (type === ConfigType.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 (!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(), @@ -508,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 diff --git a/packages/backend-core/src/environment.ts b/packages/backend-core/src/environment.ts index 51ab101b3c..60cf5b7882 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,13 +25,16 @@ const DefaultBucketName = { APPS: "prod-budi-app-assets", TEMPLATES: "templates", GLOBAL: "global", - CLOUD: "prod-budi-tenant-uploads", PLUGINS: "plugins", } const environment = { isTest, + isJest, isDev, + isProd: () => { + return !isDev() + }, JS_BCRYPT: process.env.JS_BCRYPT, JWT_SECRET: process.env.JWT_SECRET, COUCH_DB_URL: process.env.COUCH_DB_URL || "http://localhost:4005", @@ -42,6 +49,7 @@ const environment = { 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 +62,9 @@ const environment = { 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,12 +72,9 @@ const environment = { 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", LOG_LEVEL: process.env.LOG_LEVEL, @@ -87,6 +95,11 @@ for (let [key, value] of Object.entries(environment)) { // @ts-ignore environment[key] = 0 } + // handle the edge case of "false" to disable an environment variable + if (value === "false") { + // @ts-ignore + environment[key] = 0 + } } export = environment 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 2971834f0e..02c99828dd 100644 --- a/packages/backend-core/src/objectStore/index.ts +++ b/packages/backend-core/src/objectStore/index.ts @@ -1,2 +1,3 @@ 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 index 2ae8848c53..f601d40a37 100644 --- a/packages/backend-core/src/objectStore/objectStore.ts +++ b/packages/backend-core/src/objectStore/objectStore.ts @@ -8,7 +8,7 @@ import { promisify } from "util" import { join } from "path" import fs from "fs" import env from "../environment" -import { budibaseTempDir, ObjectStoreBuckets } from "./utils" +import { budibaseTempDir } from "./utils" import { v4 } from "uuid" import { APP_PREFIX, APP_DEV_PREFIX } from "../db" @@ -26,7 +26,7 @@ type UploadParams = { bucket: string filename: string path: string - type?: string + type?: string | null // can be undefined, we will remove it metadata?: { [key: string]: string | undefined @@ -41,6 +41,7 @@ const CONTENT_TYPE_MAP: any = { json: "application/json", gz: "application/gzip", } + const STRING_CONTENT_TYPES = [ CONTENT_TYPE_MAP.html, CONTENT_TYPE_MAP.css, @@ -58,35 +59,17 @@ export function sanitizeBucket(input: string) { return input.replace(new RegExp(APP_DEV_PREFIX, "g"), APP_PREFIX) } -function publicPolicy(bucketName: string) { - 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. + * @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) => { +export const ObjectStore = ( + bucket: string, + opts: { presigning: boolean } = { presigning: false } +) => { const config: any = { s3ForcePathStyle: true, signatureVersion: "v4", @@ -100,9 +83,20 @@ export const ObjectStore = (bucket: string) => { Bucket: sanitizeBucket(bucket), } } + + // custom S3 is in use i.e. minio if (env.MINIO_URL) { - config.endpoint = 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) } @@ -135,16 +129,6 @@ export const makeSureBucketExists = async (client: any, bucketName: string) => { 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.") } @@ -274,6 +258,36 @@ export const listAllObjects = async (bucketName: string, path: string) => { 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. */ @@ -315,9 +329,9 @@ export const deleteFile = async (bucketName: string, filepath: string) => { await makeSureBucketExists(objectStore, bucketName) const params = { Bucket: bucketName, - Key: filepath, + Key: sanitizeKey(filepath), } - return objectStore.deleteObject(params) + return objectStore.deleteObject(params).promise() } export const deleteFiles = async (bucketName: string, filepaths: string[]) => { @@ -326,7 +340,7 @@ export const deleteFiles = async (bucketName: string, filepaths: string[]) => { const params = { Bucket: bucketName, Delete: { - Objects: filepaths.map((path: any) => ({ Key: path })), + Objects: filepaths.map((path: any) => ({ Key: sanitizeKey(path) })), }, } return objectStore.deleteObjects(params).promise() diff --git a/packages/backend-core/src/objectStore/utils.ts b/packages/backend-core/src/objectStore/utils.ts index f3c9e93943..dba5f3d1c2 100644 --- a/packages/backend-core/src/objectStore/utils.ts +++ b/packages/backend-core/src/objectStore/utils.ts @@ -14,7 +14,6 @@ export const ObjectStoreBuckets = { 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, } diff --git a/packages/backend-core/src/redis/redlock.ts b/packages/backend-core/src/redis/redlock.ts index 586302c9b1..54b2c0a8d1 100644 --- a/packages/backend-core/src/redis/redlock.ts +++ b/packages/backend-core/src/redis/redlock.ts @@ -13,6 +13,18 @@ const getClient = async (type: LockType): Promise => { } 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}`) } @@ -41,6 +53,9 @@ export const OPTIONS = { // see https://www.awsarchitectureblog.com/2015/03/backoff.html retryJitter: 100, // time in ms }, + DELAY_500: { + retryDelay: 500, + }, } export const newRedlock = async (opts: Options = {}) => { @@ -55,19 +70,17 @@ export const doWithLock = async (opts: LockOptions, task: any) => { let lock try { // aquire lock - let name: string - if (opts.systemLock) { - name = opts.name - } else { - name = `${tenancy.getTenantId()}_${opts.name}` - } + 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 - return 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) { diff --git a/packages/backend-core/src/tenancy/tenancy.ts b/packages/backend-core/src/tenancy/tenancy.ts index e0e0703433..732402bcb7 100644 --- a/packages/backend-core/src/tenancy/tenancy.ts +++ b/packages/backend-core/src/tenancy/tenancy.ts @@ -1,4 +1,4 @@ -import { doWithDB, queryPlatformView, getGlobalDBName } from "../db" +import { doWithDB, getGlobalDBName } from "../db" import { DEFAULT_TENANT_ID, getTenantId, @@ -8,11 +8,10 @@ import { import env from "../environment" import { BBContext, - PlatformUser, TenantResolutionStrategy, GetTenantIdOptions, } from "@budibase/types" -import { Header, StaticDatabases, ViewName } from "../constants" +import { Header, StaticDatabases } from "../constants" const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name @@ -111,27 +110,7 @@ export async function lookupTenantId(userId: string) { }) } -// lookup, could be email or userId, either will return a doc -export async function getTenantUser( - 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 users = await queryPlatformView( - ViewName.PLATFORM_USERS_LOWERCASE, - { - keys: [identifier.toLowerCase()], - include_docs: true, - } - ) - if (Array.isArray(users)) { - return users[0] - } else { - return users - } -} - -export function isUserInAppTenant(appId: string, user?: any) { +export const isUserInAppTenant = (appId: string, user?: any) => { let userTenantId if (user) { userTenantId = user.tenantId || DEFAULT_TENANT_ID diff --git a/packages/backend-core/src/tests/utils.spec.js b/packages/backend-core/src/utils/tests/utils.spec.ts similarity index 61% rename from packages/backend-core/src/tests/utils.spec.js rename to packages/backend-core/src/utils/tests/utils.spec.ts index fb3828921d..bb76a93653 100644 --- a/packages/backend-core/src/tests/utils.spec.js +++ b/packages/backend-core/src/utils/tests/utils.spec.ts @@ -1,8 +1,8 @@ -const { structures } = require("../../tests") -const utils = require("../utils") -const events = require("../events") -const { DEFAULT_TENANT_ID } = require("../constants") -const { doInTenant } = require("../context") +import { structures } from "../../../tests" +import * as utils from "../../utils" +import * as events from "../../events" +import { DEFAULT_TENANT_ID } from "../../constants" +import { doInTenant } from "../../context" describe("utils", () => { describe("platformLogout", () => { @@ -14,4 +14,4 @@ describe("utils", () => { }) }) }) -}) \ No newline at end of file +}) diff --git a/packages/backend-core/src/utils/utils.ts b/packages/backend-core/src/utils/utils.ts index 3e9fbb177a..fd8d31b13f 100644 --- a/packages/backend-core/src/utils/utils.ts +++ b/packages/backend-core/src/utils/utils.ts @@ -1,6 +1,13 @@ import { getAllApps, queryGlobalView } from "../db" import { options } from "../middleware/passport/jwt" -import { Header, Cookie, MAX_VALID_DATE } from "../constants" +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" @@ -8,12 +15,11 @@ import * as events from "../events" import * as tenancy from "../tenancy" import { App, - BBContext, + Ctx, PlatformLogoutOpts, TenantResolutionStrategy, } from "@budibase/types" import { SetOption } from "cookies" -import { DocumentType, SEPARATOR, ViewName } from "../constants" const jwt = require("jsonwebtoken") const APP_PREFIX = DocumentType.APP + SEPARATOR @@ -25,7 +31,7 @@ function confirmAppId(possibleAppId: string | undefined) { : undefined } -async function resolveAppUrl(ctx: BBContext) { +async function resolveAppUrl(ctx: Ctx) { const appUrl = ctx.path.split("/")[2] let possibleAppUrl = `/${appUrl.toLowerCase()}` @@ -50,7 +56,7 @@ async function resolveAppUrl(ctx: BBContext) { return app && app.appId ? app.appId : undefined } -export function isServingApp(ctx: BBContext) { +export function isServingApp(ctx: Ctx) { // dev app if (ctx.path.startsWith(`/${APP_PREFIX}`)) { return true @@ -67,7 +73,7 @@ export function isServingApp(ctx: BBContext) { * @param {object} ctx The main request body to look through. * @returns {string|undefined} If an appId was found it will be returned. */ -export async function getAppIdFromCtx(ctx: BBContext) { +export async function getAppIdFromCtx(ctx: Ctx) { // look in headers const options = [ctx.headers[Header.APP_ID]] let appId @@ -83,12 +89,16 @@ export async function getAppIdFromCtx(ctx: BBContext) { 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 referer + const refererId = parseAppIdFromUrl(ctx.request.headers.referer) + if (!appId && refererId) { + appId = confirmAppId(refererId) } // look in the url - prod app @@ -99,6 +109,13 @@ export async function getAppIdFromCtx(ctx: BBContext) { 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. @@ -115,7 +132,7 @@ export function openJwt(token: string) { * @param {object} ctx The request which is to be manipulated. * @param {string} name The name of the cookie to get. */ -export function getCookie(ctx: BBContext, name: string) { +export function getCookie(ctx: Ctx, name: string) { const cookie = ctx.cookies.get(name) if (!cookie) { @@ -133,7 +150,7 @@ export function getCookie(ctx: BBContext, name: string) { * @param {object} opts options like whether to sign. */ export function setCookie( - ctx: BBContext, + ctx: Ctx, value: any, name = "builder", opts = { sign: true } @@ -159,7 +176,7 @@ export function setCookie( /** * Utility function, simply calls setCookie with an empty string for value */ -export function clearCookie(ctx: BBContext, name: string) { +export function clearCookie(ctx: Ctx, name: string) { setCookie(ctx, null, name) } @@ -169,7 +186,7 @@ export function clearCookie(ctx: BBContext, name: string) { * @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). */ -export function isClient(ctx: BBContext) { +export function isClient(ctx: Ctx) { return ctx.headers[Header.TYPE] === "client" } diff --git a/packages/backend-core/tests/jestSetup.ts b/packages/backend-core/tests/jestSetup.ts index 7870a721aa..b7ab5b49d9 100644 --- a/packages/backend-core/tests/jestSetup.ts +++ b/packages/backend-core/tests/jestSetup.ts @@ -17,7 +17,9 @@ env._set("MINIO_URL", "http://localhost") env._set("MINIO_ACCESS_KEY", "test") env._set("MINIO_SECRET_KEY", "test") -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/index.ts b/packages/backend-core/tests/utilities/index.ts index 65578ff013..ee96a94152 100644 --- a/packages/backend-core/tests/utilities/index.ts +++ b/packages/backend-core/tests/utilities/index.ts @@ -1,6 +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/events.ts b/packages/backend-core/tests/utilities/mocks/events.ts index 415d59019d..40c3706a55 100644 --- a/packages/backend-core/tests/utilities/mocks/events.ts +++ b/packages/backend-core/tests/utilities/mocks/events.ts @@ -117,3 +117,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/index.ts b/packages/backend-core/tests/utilities/mocks/index.ts index e71c739e26..401fd7d7a7 100644 --- a/packages/backend-core/tests/utilities/mocks/index.ts +++ b/packages/backend-core/tests/utilities/mocks/index.ts @@ -1,5 +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..1fbda5655e --- /dev/null +++ b/packages/backend-core/tests/utilities/mocks/licenses.ts @@ -0,0 +1,83 @@ +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) +} + +// 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/index.ts b/packages/backend-core/tests/utilities/structures/index.ts index 68064b9715..e0ed4df9c4 100644 --- a/packages/backend-core/tests/utilities/structures/index.ts +++ b/packages/backend-core/tests/utilities/structures/index.ts @@ -6,3 +6,4 @@ export const generator = new Chance() export * as koa from "./koa" export * as accounts from "./accounts" export * as licenses from "./licenses" +export * as plugins from "./plugins" 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.json b/packages/backend-core/tsconfig.json index ccefd149a0..1d9da5f2ae 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/yarn.lock b/packages/backend-core/yarn.lock index 0a25d5fb43..b27adcac81 100644 --- a/packages/backend-core/yarn.lock +++ b/packages/backend-core/yarn.lock @@ -470,6 +470,23 @@ 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" + +"@budibase/types@2.2.12-alpha.2": + version "2.2.12-alpha.2" + resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.2.12-alpha.2.tgz#390959555517f11dc95147518e89f400c59dd520" + integrity sha512-Dz/3kdSkBRyf+AQyaPbAwq6hxD1kQuHht+4C3pyebbHUL440ZhZhJ1o2AAFc8SF1/9SqAk/NFaktAY2vCyRlOQ== + "@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" @@ -1526,6 +1543,13 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== +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" @@ -3527,11 +3551,21 @@ json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== json5@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" - integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== + 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: +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== @@ -3827,6 +3861,11 @@ lodash@4.17.21, lodash@^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" @@ -3850,9 +3889,9 @@ ltgt@2.2.1, ltgt@^2.1.2, ltgt@~2.2.0: integrity sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA== luxon@^3.0.1: - version "3.0.4" - resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.0.4.tgz#d179e4e9f05e092241e7044f64aaa54796b03929" - integrity sha512-aV48rGUwP/Vydn8HT+5cdr26YYQiUZ42NM6ToMoaGKwYfWbfLeRkEu1wXWMHBZT6+KyLfcbbtVcoQFCbbPjKlw== + 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" @@ -4022,18 +4061,6 @@ msgpackr@^1.5.2: optionalDependencies: msgpackr-extract "^2.1.2" -nano@^10.1.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/nano/-/nano-10.1.0.tgz#afdd5a7440e62f09a8e23f41fcea328d27383922" - integrity sha512-COeN2TpLcHuSN44QLnPmfZCoCsKAg8/aelPOVqqm/2/MvRHDEA11/Kld5C4sLzDlWlhFZ3SO2WGJGevCsvcEzQ== - 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" - napi-macros@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/napi-macros/-/napi-macros-2.0.0.tgz#2b6bae421e7b96eb687aa6c77a7858640670001b" @@ -4990,13 +5017,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" diff --git a/packages/bbui/package.json b/packages/bbui/package.json index 9ba976ccc3..e99cbebb6a 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.1.43-alpha.15", + "version": "2.2.12-alpha.4", "license": "MPL-2.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", @@ -37,8 +37,8 @@ "dist" ], "dependencies": { - "@adobe/spectrum-css-workflow-icons": "^1.2.1", - "@budibase/string-templates": "2.1.43-alpha.15", + "@adobe/spectrum-css-workflow-icons": "1.2.1", + "@budibase/string-templates": "2.2.12-alpha.4", "@spectrum-css/actionbutton": "1.0.1", "@spectrum-css/actiongroup": "1.0.1", "@spectrum-css/avatar": "3.0.2", diff --git a/packages/bbui/src/Badge/Badge.svelte b/packages/bbui/src/Badge/Badge.svelte index 4bc701d983..8b54045297 100644 --- a/packages/bbui/src/Badge/Badge.svelte +++ b/packages/bbui/src/Badge/Badge.svelte @@ -10,10 +10,13 @@ export let green = false export let active = false export let inactive = false + export let hoverable = false + + diff --git a/packages/bbui/src/Form/Core/Dropzone.svelte b/packages/bbui/src/Form/Core/Dropzone.svelte index c64e69b201..e2ef4e65ce 100644 --- a/packages/bbui/src/Form/Core/Dropzone.svelte +++ b/packages/bbui/src/Form/Core/Dropzone.svelte @@ -15,6 +15,7 @@ export let value = [] export let id = null export let disabled = false + export let compact = false export let fileSizeLimit = BYTES_IN_MB * 20 export let processFiles = null export let deleteAttachments = null @@ -239,70 +240,72 @@ bind:this={fileInput} on:change={handleFile} /> - - - - - - - - - - - - - - - -

- Drag and drop your file -

+ {#if !compact} + + + + + + + + + + + + + + + +

+ Drag and drop your file +

+ {/if} {#if !disabled}

Select a file to upload -
- from your computer + {#if !compact} +
+ from your computer + {/if}

{#if fileTags.length} diff --git a/packages/bbui/src/Table/RelationshipRenderer.svelte b/packages/bbui/src/Table/RelationshipRenderer.svelte index 4db0c63d95..b70eaeb07d 100644 --- a/packages/bbui/src/Table/RelationshipRenderer.svelte +++ b/packages/bbui/src/Table/RelationshipRenderer.svelte @@ -1,6 +1,7 @@
@@ -172,7 +178,7 @@ {/key}
diff --git a/packages/builder/src/components/backend/DataTable/Table.svelte b/packages/builder/src/components/backend/DataTable/Table.svelte index 7c9b5bcf3d..c82a9656e1 100644 --- a/packages/builder/src/components/backend/DataTable/Table.svelte +++ b/packages/builder/src/components/backend/DataTable/Table.svelte @@ -216,7 +216,6 @@ flex-direction: row; justify-content: flex-start; align-items: center; - margin-top: var(--spacing-m); } .table-title > div { margin-left: var(--spacing-xs); diff --git a/packages/builder/src/components/backend/DataTable/buttons/ExistingRelationshipButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/ExistingRelationshipButton.svelte index 4a7abf487f..e188783f7d 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/ExistingRelationshipButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/ExistingRelationshipButton.svelte @@ -1,7 +1,7 @@ diff --git a/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte b/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte index c860c7cba3..b4e713ce3c 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte @@ -1,7 +1,5 @@ @@ -157,7 +181,7 @@ Connect to an external datasource
- {#each Object.entries(integrations).filter(([key, val]) => key !== IntegrationTypes.INTERNAL && !val.custom) as [integrationType, schema]} + {#each sortedIntegrations.filter(([key, val]) => key !== IntegrationTypes.INTERNAL && !val.custom) as [integrationType, schema]} selectIntegration(evt.detail)} {schema} diff --git a/packages/builder/src/components/backend/DatasourceNavigator/modals/ImportRestQueriesModal.svelte b/packages/builder/src/components/backend/DatasourceNavigator/modals/ImportRestQueriesModal.svelte index 82721e4ab1..82a2aa8066 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/modals/ImportRestQueriesModal.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/modals/ImportRestQueriesModal.svelte @@ -64,7 +64,6 @@ // reload await datasources.fetch() await queries.fetch() - await datasources.select(datasourceId) if (navigateDatasource) { $goto(`./datasource/${datasourceId}`) diff --git a/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditDatasourcePopover.svelte b/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditDatasourcePopover.svelte index 165ed18ad8..344bfab4e4 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditDatasourcePopover.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditDatasourcePopover.svelte @@ -1,6 +1,6 @@ {#if $database?._id} @@ -44,8 +23,9 @@ border={idx > 0} icon={table._id === TableNames.USERS ? "UserGroup" : "Table"} text={table.name} - selected={$tables.selected?._id === table._id} - on:click={() => selectTable(table)} + selected={$isActive("./table/:tableId") && + $tables.selected?._id === table._id} + on:click={() => $goto(`./table/${table._id}`)} > {#if table._id !== TableNames.USERS} @@ -56,8 +36,8 @@ indentLevel={2} icon="Remove" text={viewName} - selected={selectedView === viewName} - on:click={() => onClickView(table, viewName)} + selected={$isActive("./view") && $views.selected?.name === viewName} + on:click={() => $goto(`./view/${encodeURIComponent(viewName)}`)} > - import { goto } from "@roxi/routify" + import { goto, params } from "@roxi/routify" import { store } from "builderStore" + import { cloneDeep } from "lodash/fp" import { tables, datasources } from "stores/backend" import { ActionMenu, @@ -18,7 +19,10 @@ let editorModal let confirmDeleteDialog let error = "" - let originalName = table.name + + let originalName + let updatedName + let templateScreens let willBeDeleted let deleteTableName @@ -37,17 +41,16 @@ } async function deleteTable() { - const wasSelectedTable = $tables.selected + const isSelected = $params.tableId === table._id try { await tables.delete(table) await store.actions.screens.delete(templateScreens) - await tables.fetch() if (table.type === "external") { await datasources.fetch() } notifications.success("Table deleted") - if (wasSelectedTable && wasSelectedTable._id === table._id) { - $goto("./table") + if (isSelected) { + $goto(`./datasource/${table.datasourceId}`) } } catch (error) { notifications.error("Error deleting table") @@ -59,7 +62,9 @@ } async function save() { - await tables.save(table) + const updatedTable = cloneDeep(table) + updatedTable.name = updatedName + await tables.save(updatedTable) notifications.success("Table renamed successfully") } @@ -70,6 +75,11 @@ ? `Table with name ${tableName} already exists. Please choose another name.` : "" } + + const initForm = () => { + originalName = table.name + "" + updatedName = table.name + "" + } {#if allowDeletion} @@ -84,17 +94,17 @@ {/if} - + diff --git a/packages/builder/src/components/backend/TableNavigator/popovers/EditViewPopover.svelte b/packages/builder/src/components/backend/TableNavigator/popovers/EditViewPopover.svelte index 6eba5070fc..b637f46f5e 100644 --- a/packages/builder/src/components/backend/TableNavigator/popovers/EditViewPopover.svelte +++ b/packages/builder/src/components/backend/TableNavigator/popovers/EditViewPopover.svelte @@ -1,6 +1,7 @@ @@ -46,9 +60,9 @@ Edit Delete - + - + - - - - .panel { width: 260px; + flex: 0 0 260px; background: var(--background); display: flex; flex-direction: column; @@ -66,6 +67,7 @@ } .panel.wide { width: 420px; + flex: 0 0 420px; } .header { flex: 0 0 48px; diff --git a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/PromptUser.svelte b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/PromptUser.svelte new file mode 100644 index 0000000000..85d395e4f4 --- /dev/null +++ b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/PromptUser.svelte @@ -0,0 +1,50 @@ + + +
+ Enter the message you wish to display to the user. +
+ + + + +
+
+ + diff --git a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/index.js b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/index.js index 4a9640312d..90ce1607e4 100644 --- a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/index.js +++ b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/index.js @@ -16,5 +16,6 @@ export { default as ExportData } from "./ExportData.svelte" export { default as ContinueIf } from "./ContinueIf.svelte" export { default as UpdateFieldValue } from "./UpdateFieldValue.svelte" export { default as ShowNotification } from "./ShowNotification.svelte" +export { default as PromptUser } from "./PromptUser.svelte" export { default as OpenSidePanel } from "./OpenSidePanel.svelte" export { default as CloseSidePanel } from "./CloseSidePanel.svelte" diff --git a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/manifest.json b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/manifest.json index 521ad85f0a..7497990304 100644 --- a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/manifest.json +++ b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/manifest.json @@ -117,6 +117,11 @@ "component": "ShowNotification", "dependsOnFeature": "showNotificationAction" }, + { + "name": "Prompt User", + "type": "application", + "component": "PromptUser" + }, { "name": "Open Side Panel", "type": "application", diff --git a/packages/builder/src/components/design/settings/controls/DataSourceSelect.svelte b/packages/builder/src/components/design/settings/controls/DataSourceSelect.svelte index 55a9a30eb3..6269599f40 100644 --- a/packages/builder/src/components/design/settings/controls/DataSourceSelect.svelte +++ b/packages/builder/src/components/design/settings/controls/DataSourceSelect.svelte @@ -144,6 +144,11 @@ drawer.show() } + const getQueryValue = queries => { + value = queries.find(q => q._id === value._id) || value + return value + } + const saveQueryParams = () => { handleSelected({ ...value, @@ -175,7 +180,7 @@ {/if} field.name) || [] $: valueTypeOptions = allowBindings ? ["Value", "Binding"] : ["Value"] @@ -119,7 +115,11 @@ const santizeOperator = filter => { // Ensure a valid operator is selected - const operators = getValidOperatorsForType(filter.type).map(x => x.value) + const operators = getValidOperatorsForType( + filter.type, + filter.field, + datasource + ).map(x => x.value) if (!operators.includes(filter.operator)) { filter.operator = operators[0] ?? OperatorOptions.Equals.value } @@ -201,7 +201,11 @@ /> -
- {#if queryConfig} -
- -
- Add a JavaScript function to transform the query result. - (query.transformer = e.detail)} - /> - - -
- Results - - - - -
- - Below, you can preview the results from your query and change the schema. - -
- {#if data} - - - - - - - - - - - + {#if queryConfig} +
+ + onOperatorChange(filter, e.detail)} placeholder={null} diff --git a/packages/client/src/components/app/forms/AttachmentField.svelte b/packages/client/src/components/app/forms/AttachmentField.svelte index da04c57e82..e24115ebc0 100644 --- a/packages/client/src/components/app/forms/AttachmentField.svelte +++ b/packages/client/src/components/app/forms/AttachmentField.svelte @@ -6,6 +6,7 @@ export let field export let label export let disabled = false + export let compact = false export let validation export let extensions export let onChange @@ -89,6 +90,7 @@ {handleTooManyFiles} {maximum} {extensions} + {compact} /> {/if}
@@ -96,6 +98,6 @@ diff --git a/packages/client/src/components/overlay/PeekScreenDisplay.svelte b/packages/client/src/components/overlay/PeekScreenDisplay.svelte index b9f4914624..6e0fa81b43 100644 --- a/packages/client/src/components/overlay/PeekScreenDisplay.svelte +++ b/packages/client/src/components/overlay/PeekScreenDisplay.svelte @@ -61,7 +61,7 @@ if (messageHandler) { messageHandler(message) } else { - console.warning("Unknown event type", message?.data?.type) + console.warn("Unknown event type", message?.data?.type) } } diff --git a/packages/client/src/utils/buttonActions.js b/packages/client/src/utils/buttonActions.js index e471518285..06bc6f356a 100644 --- a/packages/client/src/utils/buttonActions.js +++ b/packages/client/src/utils/buttonActions.js @@ -327,6 +327,8 @@ const showNotificationHandler = action => { notificationStore.actions[type]?.(message, autoDismiss) } +const promptUserHandler = () => {} + const OpenSidePanelHandler = action => { const { id } = action.parameters if (id) { @@ -357,6 +359,7 @@ const handlerMap = { ["Export Data"]: exportDataHandler, ["Continue if / Stop if"]: continueIfHandler, ["Show Notification"]: showNotificationHandler, + ["Prompt User"]: promptUserHandler, ["Open Side Panel"]: OpenSidePanelHandler, ["Close Side Panel"]: CloseSidePanelHandler, } @@ -366,6 +369,7 @@ const confirmTextMap = { ["Save Row"]: "Are you sure you want to save this row?", ["Execute Query"]: "Are you sure you want to execute this query?", ["Trigger Automation"]: "Are you sure you want to trigger this automation?", + ["Prompt User"]: "Are you sure you want to contiune?", } /** @@ -417,8 +421,12 @@ export const enrichButtonActions = (actions, context) => { return new Promise(resolve => { const defaultText = confirmTextMap[action["##eventHandlerType"]] const confirmText = action.parameters?.confirmText || defaultText + + const defaultTitleText = action["##eventHandlerType"] + const customTitleText = + action.parameters?.customTitleText || defaultTitleText confirmationStore.actions.showConfirmation( - action["##eventHandlerType"], + customTitleText, confirmText, async () => { // When confirmed, execute this action immediately, @@ -429,7 +437,7 @@ export const enrichButtonActions = (actions, context) => { buttonContext.push(result) const newContext = { ...context, actions: buttonContext } - // Enrich and call the next button action + // Enrich and call the next button action if there is more than one action remaining const next = enrichButtonActions( actions.slice(i + 1), newContext diff --git a/packages/client/yarn.lock b/packages/client/yarn.lock index aebda2efd0..1dd9c2278e 100644 --- a/packages/client/yarn.lock +++ b/packages/client/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@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== + "@babel/code-frame@^7.10.4": version "7.16.0" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.0.tgz#0dfc80309beec8411e65e706461c408b0bb9b431" @@ -23,6 +28,105 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@budibase/bbui@2.2.10-alpha.13": + version "2.2.10-alpha.13" + resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-2.2.10-alpha.13.tgz#42bf24c5df4cea74a69fba571423fcf4527f16fa" + integrity sha512-l6TYaqizIxjIonqgyYewsEvWK+H+xwKTrJrQTHSsw5MWxKY+MZoh+i+pUM5tQKwpG0Z6kg3iZSYvm0GUk3HGFw== + dependencies: + "@adobe/spectrum-css-workflow-icons" "1.2.1" + "@budibase/string-templates" "2.2.10-alpha.13" + "@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" + dayjs "^1.10.4" + easymde "^2.16.1" + svelte-flatpickr "^3.2.3" + svelte-portal "^1.0.0" + +"@budibase/frontend-core@2.2.10-alpha.13": + version "2.2.10-alpha.13" + resolved "https://registry.yarnpkg.com/@budibase/frontend-core/-/frontend-core-2.2.10-alpha.13.tgz#b6f49e65532eee31a19a6991740c9f55220271a9" + integrity sha512-iXDtSec2VHbDFwLkXOm7L8oah1CDBd3rrNbEnkL/8FyAIeguLp5iSwojtTWZ+wn+ZRa7yLwQDdqXIGVkj1XahA== + dependencies: + "@budibase/bbui" "2.2.10-alpha.13" + lodash "^4.17.21" + svelte "^3.46.2" + +"@budibase/handlebars-helpers@^0.11.8": + version "0.11.8" + resolved "https://registry.yarnpkg.com/@budibase/handlebars-helpers/-/handlebars-helpers-0.11.8.tgz#6953d29673a8c5c407e096c0a84890465c7ce841" + integrity sha512-ggWJUt0GqsHFAEup5tlWlcrmYML57nKhpNGGLzVsqXVYN8eVmf3xluYmmMe7fDYhQH0leSprrdEXmsdFQF3HAQ== + dependencies: + array-sort "^1.0.0" + define-property "^2.0.2" + extend-shallow "^3.0.2" + for-in "^1.0.2" + get-object "^0.2.0" + get-value "^3.0.1" + handlebars "^4.7.7" + handlebars-utils "^1.0.6" + has-value "^2.0.2" + helper-md "^0.2.2" + html-tag "^2.0.0" + is-even "^1.0.0" + is-glob "^4.0.1" + kind-of "^6.0.3" + micromatch "^3.1.5" + relative "^3.0.2" + striptags "^3.1.1" + to-gfm-code-block "^0.1.1" + year "^0.2.1" + +"@budibase/string-templates@2.2.10-alpha.13": + version "2.2.10-alpha.13" + resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-2.2.10-alpha.13.tgz#5b8c21542c0f9e7ec23ae266dbf7de30b45e82e2" + integrity sha512-RbFz9ARAqx/LrR74QTy/DuDRFkOsUkQh5sHpiecpmTSvGAXcS7NGDGSKQUaDrDP0pCtYrBD5mfEeFdD00YdYrQ== + dependencies: + "@budibase/handlebars-helpers" "^0.11.8" + dayjs "^1.10.4" + handlebars "^4.7.6" + handlebars-utils "^1.0.6" + lodash "^4.17.20" + vm2 "^3.9.4" + "@jridgewell/gen-mapping@^0.3.0": version "0.3.2" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" @@ -118,16 +222,58 @@ resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== +"@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/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.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/button/-/button-3.0.1.tgz#6db8c3e851baecd0f1c2d88fef37d49d01c6e643" + integrity sha512-YXrBtjIYisk4Vaxnp0RiE4gdElQX04P2mc4Pi2GlQ27dJKlHmufYcF+kAqGdtiyK5yjdN/vKRcC8y13aA4rusA== + "@spectrum-css/button@^3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@spectrum-css/button/-/button-3.0.3.tgz#2df1efaab6c7e0b3b06cb4b59e1eae59c7f1fc84" integrity sha512-6CnLPqqtaU/PcSSIGeGRi0iFIIxIUByYLKFO6zn5NEUc12KQ28dJ4PLwB6WBa0L8vRoAGlnWWH2ZZweTijbXgg== +"@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/card@^3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@spectrum-css/card/-/card-3.0.3.tgz#56b2e2da6b80c1583228baa279de7407383bfb6b" integrity sha512-+oKLUI2a0QmQP9EzySeq/G4FpUkkdaDNbuEbqCj2IkPMc/2v/nwzsPhh1fj2UIghGAiiUwXfPpzax1e8fyhQUg== +"@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.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": + 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/divider@^1.0.3": version "1.0.17" resolved "https://registry.yarnpkg.com/@spectrum-css/divider/-/divider-1.0.17.tgz#cae86fdcb5eb6dae95798ae19ec962e5735fc27f" @@ -135,11 +281,66 @@ dependencies: "@spectrum-css/vars" "^7.0.0" +"@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": + 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.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.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": + 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": + 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": + 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": + 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.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/link/-/link-3.1.1.tgz#cb526a2e10b50ef5a7ae29cca7272e2610d597eb" + integrity sha512-Bi88lRhTY7g6nM/ryW1yY4Cji211ZYNtRxkxbV7n2lPvwMAAQtyx0qVD3ru4kTGj/FFVvmPR3XiOE10K13HSNA== + "@spectrum-css/link@^3.1.3": version "3.1.17" resolved "https://registry.yarnpkg.com/@spectrum-css/link/-/link-3.1.17.tgz#efa8fd5ee8a6b291843dcaf4fa1270b81c564c48" integrity sha512-sWWTnDB+Yig9WmLvzcvUgSH6zZtu2tWfobMivFLjRnfQYIhxJSoj87AleLpcTbvIQIwSwytSdnbncsm4rBfDjg== +"@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.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/page@^3.0.1": version "3.0.9" resolved "https://registry.yarnpkg.com/@spectrum-css/page/-/page-3.0.9.tgz#f8a705dee90af958e2ee20307218e4f82a018c36" @@ -147,17 +348,127 @@ dependencies: "@spectrum-css/vars" "^4.3.1" +"@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.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.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": + 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": + 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": + 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": + 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": + 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== + +"@spectrum-css/slider@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/slider/-/slider-3.0.1.tgz#5281e6f47eb5a4fd3d1816c138bf66d01d7f2e49" + integrity sha512-DI2dtMRnQuDM1miVzl3SGyR1khUEKnwdXfO5EHDFwkC3yav43F5QogkfjmjFmWWobMVovdJlAuiaaJ/IHejD0Q== + +"@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": + 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": + 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.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": + 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/tag@^3.1.4": version "3.3.5" resolved "https://registry.yarnpkg.com/@spectrum-css/tag/-/tag-3.3.5.tgz#f83ded301c8ed334d2c9a33f586807a3d423e588" integrity sha512-KhUZ75xzjnbYPg1ztwWp5xvWYVkG5+KH/2OiTsE5vA9LXZ3Z1O21c5YpFg5FVCvAWN/fwfwpxsi7sp38x7FlLg== +"@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.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.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": + 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": + 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.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/typography/-/typography-3.0.1.tgz#957dafd9b18c314fa37a88b549042ba2175f5b3f" + integrity sha512-XyR68K2rIZX3u4j7HhMLOqLVHDJZcapp3XUqgYMzMWccBFleA0qPxKpfRWqVIA5DzTMSIw0wEcZPYKWFZ2e6dA== + "@spectrum-css/typography@^3.0.2": 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/vars@^3.0.1": +"@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": + 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.1", "@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== @@ -177,11 +488,23 @@ resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== +"@types/codemirror@^5.60.4": + version "5.60.6" + resolved "https://registry.yarnpkg.com/@types/codemirror/-/codemirror-5.60.6.tgz#0511ed51d087ad2a2eedb6111fc74e739c4549f6" + integrity sha512-JIDPSvkYRlcv/2F0erqD+de2ni/Mz6FJMEGb0vwF6ByQOcHIKfiEfwrO4d6dSRwYeHyNUMpGjev0PyjX2M0XWw== + dependencies: + "@types/tern" "*" + "@types/estree@*", "@types/estree@0.0.39": version "0.0.39" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== +"@types/marked@^4.0.7": + version "4.0.8" + resolved "https://registry.yarnpkg.com/@types/marked/-/marked-4.0.8.tgz#b316887ab3499d0a8f4c70b7bd8508f92d477955" + integrity sha512-HVNzMT5QlWCOdeuBsgXP8EZzKUf0+AXzN+sLmjvaB3ZlLqO+e4u0uXrdw9ub69wBKFs+c6/pA4r9sy6cCDvImw== + "@types/node@*": version "16.11.7" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.7.tgz#36820945061326978c42a01e56b61cd223dfdc42" @@ -194,11 +517,28 @@ dependencies: "@types/node" "*" +"@types/tern@*": + version "0.23.4" + resolved "https://registry.yarnpkg.com/@types/tern/-/tern-0.23.4.tgz#03926eb13dbeaf3ae0d390caf706b2643a0127fb" + integrity sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg== + dependencies: + "@types/estree" "*" + +acorn-walk@^8.2.0: + 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@^8.5.0: version "8.7.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== +acorn@^8.7.0: + version "8.8.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73" + integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA== + alphanum-sort@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" @@ -235,11 +575,77 @@ apexcharts@^3.19.2, apexcharts@^3.22.1: svg.resize.js "^1.4.3" svg.select.js "^3.0.1" +argparse@^1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + integrity sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA== + +arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + integrity sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q== + +array-sort@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-sort/-/array-sort-1.0.0.tgz#e4c05356453f56f53512a7d1d6123f2c54c0a88a" + integrity sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg== + dependencies: + default-compare "^1.0.0" + get-value "^2.0.6" + kind-of "^5.0.2" + +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + integrity sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ== + +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + integrity sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw== + +atob@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + +autolinker@~0.28.0: + version "0.28.1" + resolved "https://registry.yarnpkg.com/autolinker/-/autolinker-0.28.1.tgz#0652b491881879f0775dace0cdca3233942a4e47" + integrity sha512-zQAFO1Dlsn69eXaO6+7YZc+v84aquQKbwpzCE3L0stj56ERn9hutFxPopViLjo9G+rWwjozRhgS5KJ25Xy19cQ== + dependencies: + gulp-header "^1.7.1" + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -258,6 +664,22 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +braces@^2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + browserslist@^4.0.0, browserslist@^4.16.0, browserslist@^4.16.6: version "4.18.1" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.18.1.tgz#60d3920f25b6860eb917c6c7b185576f4d8b017f" @@ -279,6 +701,21 @@ builtin-modules@^3.1.0: resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887" integrity sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA== +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + caniuse-api@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" @@ -311,6 +748,16 @@ chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + cliui@^7.0.2: version "7.0.4" resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" @@ -320,6 +767,26 @@ cliui@^7.0.2: strip-ansi "^6.0.0" wrap-ansi "^7.0.0" +codemirror-spell-checker@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/codemirror-spell-checker/-/codemirror-spell-checker-1.1.2.tgz#1c660f9089483ccb5113b9ba9ca19c3f4993371e" + integrity sha512-2Tl6n0v+GJRsC9K3MLCdLaMOmvWL0uukajNJseorZJsslaxZyZMgENocPU8R0DyoTAiKsyqiemSOZo7kjGV0LQ== + dependencies: + typo-js "*" + +codemirror@^5.63.1: + version "5.65.11" + resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.65.11.tgz#c818edc3274788c008f636520c5490a1f7009b4f" + integrity sha512-Gp62g2eKSCHYt10axmGhKq3WoJSvVpvhXmowNq7pZdRVowwtvBR/hi2LSP5srtctKkRT33T6/n8Kv1UGp7JW4A== + +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + integrity sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw== + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -364,18 +831,33 @@ commondir@^1.0.1: resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= +component-emitter@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -concat-with-sourcemaps@^1.1.0: +concat-with-sourcemaps@*, concat-with-sourcemaps@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz#d4ea93f05ae25790951b99e7b3b09e3908a4082e" integrity sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg== dependencies: source-map "^0.6.1" +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw== + +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== + css-declaration-sorter@^6.0.3: version "6.1.3" resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.1.3.tgz#e9852e4cf940ba79f509d9425b137d1f94438dc2" @@ -469,11 +951,23 @@ csso@^4.2.0: dependencies: css-tree "^1.1.2" +dayjs@^1.10.4: + version "1.11.7" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2" + integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ== + dayjs@^1.10.5: version "1.10.8" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.8.tgz#267df4bc6276fcb33c04a6735287e3f429abec41" integrity sha512-wbNwDfBHHur9UOzNUjeKUOJ0fCb0a52Wx0xInmQ7Y8FstyajiV1NmK1e00cxsr9YrE9r7yAChE0VvpuY5Rnlow== +debug@^2.2.0, debug@^2.3.3: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + debug@~4.3.1, debug@~4.3.2: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" @@ -481,16 +975,50 @@ debug@~4.3.1, debug@~4.3.2: dependencies: ms "2.1.2" +decode-uri-component@^0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" + integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== + deepmerge@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== +default-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/default-compare/-/default-compare-1.0.0.tgz#cb61131844ad84d84788fb68fd01681ca7781a2f" + integrity sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ== + dependencies: + kind-of "^5.0.2" + define-lazy-prop@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + integrity sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA== + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + integrity sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA== + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + dom-serializer@^1.0.1: version "1.3.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91" @@ -533,6 +1061,17 @@ downloadjs@1.4.7: resolved "https://registry.yarnpkg.com/downloadjs/-/downloadjs-1.4.7.tgz#f69f96f940e0d0553dac291139865a3cd0101e3c" integrity sha1-9p+W+UDg0FU9rCkROYZaPNAQHjw= +easymde@^2.16.1: + version "2.18.0" + resolved "https://registry.yarnpkg.com/easymde/-/easymde-2.18.0.tgz#ff1397d07329b1a7b9187d2d0c20766fa16b3b1b" + integrity sha512-IxVVUxNWIoXLeqtBU4BLc+eS/ScYhT1Dcb6yF5Wchoj1iXAV+TIIDWx+NCaZhY7RcSHqDPKllbYq7nwGKILnoA== + dependencies: + "@types/codemirror" "^5.60.4" + "@types/marked" "^4.0.7" + codemirror "^5.63.1" + codemirror-spell-checker "1.1.2" + marked "^4.1.0" + electron-to-chromium@^1.3.896: version "1.3.900" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.900.tgz#5be2c5818a2a012c511b4b43e87b6ab7a296d4f5" @@ -564,6 +1103,11 @@ engine.io-parser@~5.0.3: resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.0.4.tgz#0b13f704fa9271b3ec4f33112410d8f3f41d0fc0" integrity sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg== +ent@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d" + integrity sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA== + entities@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" @@ -609,11 +1153,80 @@ eventemitter3@^4.0.4: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + integrity sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA== + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug== + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + integrity sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q== + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + integrity sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ== + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + flatpickr@^4.5.2: version "4.6.9" resolved "https://registry.yarnpkg.com/flatpickr/-/flatpickr-4.6.9.tgz#9a13383e8a6814bda5d232eae3fcdccb97dc1499" integrity sha512-F0azNNi8foVWKSF+8X+ZJzz8r9sE1G4hl06RyceIaLvyltKvDl6vqk9Lm/6AUUCi5HWaIjiUbk7UpeE/fOXOpw== +for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ== + +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + integrity sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA== + dependencies: + map-cache "^0.2.2" + +fs-exists-sync@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add" + integrity sha512-cR/vflFyPZtrN6b38ZyWxpWdhlXrzZEBawlpBQMq7033xVY7/kg0GDMBK5jg8lDYQckdJ5x/YC88lM3C7VMsLg== + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -641,6 +1254,26 @@ 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-object@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/get-object/-/get-object-0.2.0.tgz#d92ff7d5190c64530cda0543dac63a3d47fe8c0c" + integrity sha512-7P6y6k6EzEFmO/XyUyFlXm1YLJy9xeA1x/grNV8276abX5GuwUtYgKFkRFkLixw4hf4Pz9q2vgv/8Ar42R0HuQ== + dependencies: + is-number "^2.0.2" + isobject "^0.2.0" + +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + integrity sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA== + +get-value@^3.0.0, get-value@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-3.0.1.tgz#5efd2a157f1d6a516d7524e124ac52d0a39ef5a8" + integrity sha512-mKZj9JLQrwMBtj5wxi6MH8Z5eSKaERpAwjg43dPtlGI1ZVEgH/qC7T8/6R2OBSUA+zzHBZgICsVJaEIV2tKTDA== + dependencies: + isobject "^3.0.1" + glob@^7.1.6: version "7.2.0" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" @@ -653,6 +1286,35 @@ glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" +gulp-header@^1.7.1: + version "1.8.12" + resolved "https://registry.yarnpkg.com/gulp-header/-/gulp-header-1.8.12.tgz#ad306be0066599127281c4f8786660e705080a84" + integrity sha512-lh9HLdb53sC7XIZOYzTXM4lFuXElv3EVkSDhsd7DoJBj7hm+Ni7D3qYbb+Rr8DuM8nRanBvkVO9d7askreXGnQ== + dependencies: + concat-with-sourcemaps "*" + lodash.template "^4.4.0" + through2 "^2.0.0" + +handlebars-utils@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/handlebars-utils/-/handlebars-utils-1.0.6.tgz#cb9db43362479054782d86ffe10f47abc76357f9" + integrity sha512-d5mmoQXdeEqSKMtQQZ9WkiUcO1E3tPbWxluCK9hVgIDPzQa9WsKo3Lbe/sGflTe7TomHEeZaOgwIkyIr1kfzkw== + dependencies: + kind-of "^6.0.0" + typeof-article "^0.1.1" + +handlebars@^4.7.6, handlebars@^4.7.7: + version "4.7.7" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" + integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.0" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -663,6 +1325,52 @@ 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-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + integrity sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q== + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + integrity sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw== + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-value@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-2.0.2.tgz#d0f12e8780ba8e90e66ad1a21c707fdb67c25658" + integrity sha512-ybKOlcRsK2MqrM3Hmz/lQxXHZ6ejzSPzpNabKB45jb5qDgJvKPa3SdapTsTLwEb9WltgWpOmNax7i+DzNOk4TA== + dependencies: + get-value "^3.0.0" + has-values "^2.0.1" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + integrity sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ== + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + integrity sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ== + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +has-values@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-2.0.1.tgz#3876200ff86d8a8546a9264a952c17d5fc17579d" + integrity sha512-+QdH3jOmq9P8GfdjFg0eJudqx1FqU62NQJ4P16rOEHeRdl7ckgwn6uqQjzYE0ZoHVV/e5E2esuJ5Gl5+HUW19w== + dependencies: + kind-of "^6.0.2" + has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -670,6 +1378,24 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +helper-md@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/helper-md/-/helper-md-0.2.2.tgz#c1f59d7e55bbae23362fd8a0e971607aec69d41f" + integrity sha512-49TaQzK+Ic7ZVTq4i1UZxRUJEmAilTk8hz7q4I0WNUaTclLR8ArJV5B3A1fe1xF2HtsDTr2gYKLaVTof/Lt84Q== + dependencies: + ent "^2.2.0" + extend-shallow "^2.0.1" + fs-exists-sync "^0.1.0" + remarkable "^1.6.2" + +html-tag@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/html-tag/-/html-tag-2.0.0.tgz#36c3bc8d816fd30b570d5764a497a641640c2fed" + integrity sha512-XxzooSo6oBoxBEUazgjdXj7VwTn/iSTSZzTYKzYY6I916tkaYzypHxy+pbVU1h+0UQ9JlVf5XkNQyxOAiiQO1g== + dependencies: + is-self-closing "^1.0.1" + kind-of "^6.0.0" + html5-qrcode@^2.2.1: version "2.2.3" resolved "https://registry.yarnpkg.com/html5-qrcode/-/html5-qrcode-2.2.3.tgz#5acb826860365e7c7ab91e1e14528ea16a502e8a" @@ -717,7 +1443,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2: +inherits@2, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -727,6 +1453,25 @@ is-absolute-url@^3.0.3: resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + integrity sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A== + dependencies: + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== + dependencies: + kind-of "^6.0.0" + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + is-core-module@^2.2.0: version "2.8.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.0.tgz#0321336c3d0925e497fd97f5d95cb114a5ccd548" @@ -734,21 +1479,112 @@ is-core-module@^2.2.0: dependencies: has "^1.0.3" +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + integrity sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg== + dependencies: + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== + dependencies: + kind-of "^6.0.0" + +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + is-docker@^2.0.0, is-docker@^2.1.1: version "2.2.1" resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== +is-even@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-even/-/is-even-1.0.0.tgz#76b5055fbad8d294a86b6a949015e1c97b717c06" + integrity sha512-LEhnkAdJqic4Dbqn58A0y52IXoHWlsueqQkKfMfdEnIYG8A1sm/GHidKkS6yvXlMoRrkM34csHnXQtOqcb+Jzg== + dependencies: + is-odd "^0.1.2" + +is-extendable@^0.1.0, is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw== + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== + dependencies: + is-plain-object "^2.0.4" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + is-fullwidth-code-point@^3.0.0: version "3.0.0" 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.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-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= +is-number@^2.0.2: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" + integrity sha512-QUzH43Gfb9+5yckcrSA0VBDwEtDUchrk4F6tfJZQuNzDJbEDB9cZNzSfXGQ1jqmdDY/kl41lUOWM9syA8z8jlg== + dependencies: + kind-of "^3.0.2" + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + integrity sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg== + dependencies: + kind-of "^3.0.2" + +is-odd@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-0.1.2.tgz#bc573b5ce371ef2aad6e6f49799b72bef13978a7" + integrity sha512-Ri7C2K7o5IrUU9UEI8losXJCCD/UtsaIrkR5sxIcFg4xQ9cRJXlWA5DQvTE0yDc0krvSNLsRGXN11UPS6KyfBw== + dependencies: + is-number "^3.0.0" + +is-plain-object@^2.0.3, is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + is-plain-object@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" @@ -766,6 +1602,18 @@ is-resolvable@^1.1.0: resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== +is-self-closing@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-self-closing/-/is-self-closing-1.0.1.tgz#5f406b527c7b12610176320338af0fa3896416e4" + integrity sha512-E+60FomW7Blv5GXTlYee2KDrnG6srxF7Xt1SjrhWUGUEsTFIqY/nq2y3DaftCsgUMdh89V07IVfhY9KIJhLezg== + dependencies: + self-closing-tags "^1.0.1" + +is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" @@ -773,6 +1621,28 @@ is-wsl@^2.2.0: dependencies: is-docker "^2.0.0" +isarray@1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + +isobject@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-0.2.0.tgz#a3432192f39b910b5f02cc989487836ec70aa85e" + integrity sha512-VaWq6XYAsbvM0wf4dyBO7WH9D7GosB7ZZlqrawI9BBiTMINBeCyqSKBa35m870MY3O4aM31pYyZi9DfGrYMJrQ== + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + integrity sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA== + dependencies: + isarray "1.0.0" + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== + jest-worker@^26.2.1: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" @@ -788,12 +1658,36 @@ js-tokens@^4.0.0: integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== 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" +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.1.0, kind-of@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + integrity sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ== + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + integrity sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw== + dependencies: + is-buffer "^1.1.5" + +kind-of@^5.0.0, kind-of@^5.0.2: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== + +kind-of@^6.0.0, kind-of@^6.0.2, 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== + leaflet@^1.7.1: version "1.7.1" resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.7.1.tgz#10d684916edfe1bf41d688a3b97127c0322a2a19" @@ -813,6 +1707,11 @@ loader-utils@1.4.1, loader-utils@^1.1.0: emojis-list "^3.0.0" json5 "^1.0.1" +lodash._reinterpolate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" + integrity sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA== + lodash.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" @@ -823,11 +1722,31 @@ lodash.memoize@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= +lodash.template@^4.4.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" + integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== + dependencies: + lodash._reinterpolate "^3.0.0" + lodash.templatesettings "^4.0.0" + +lodash.templatesettings@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33" + integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ== + dependencies: + lodash._reinterpolate "^3.0.0" + lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= +lodash@^4.17.20, lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + magic-string@^0.25.7: version "0.25.7" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" @@ -835,6 +1754,23 @@ magic-string@^0.25.7: dependencies: sourcemap-codec "^1.4.4" +map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + integrity sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg== + +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + integrity sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w== + dependencies: + object-visit "^1.0.0" + +marked@^4.1.0: + version "4.2.5" + resolved "https://registry.yarnpkg.com/marked/-/marked-4.2.5.tgz#979813dfc1252cc123a79b71b095759a32f42a5d" + integrity sha512-jPueVhumq7idETHkb203WDD4fMA3yV9emQ5vLwop58lu8bTclMghBWcYAavlDqIEMaisADinV1TooIFCfqOsYQ== + mdn-data@2.0.14: version "2.0.14" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" @@ -845,6 +1781,25 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== +micromatch@^3.1.5: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + minimatch@^3.0.2, minimatch@^3.0.4: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -852,11 +1807,24 @@ minimatch@^3.0.2, minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -minimist@^1.2.0: +minimist@^1.2.0, minimist@^1.2.5: version "1.2.7" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== +mixin-deep@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" + integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + +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" @@ -877,6 +1845,28 @@ nanoid@^3.3.1: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.2.tgz#c89622fafb4381cd221421c69ec58547a1eec557" integrity sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA== +nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +neo-async@^2.6.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + node-releases@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5" @@ -894,6 +1884,29 @@ nth-check@^2.0.0: dependencies: boolbase "^1.0.0" +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + integrity sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ== + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + integrity sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA== + dependencies: + isobject "^3.0.0" + +object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + integrity sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ== + dependencies: + isobject "^3.0.1" + once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -935,6 +1948,11 @@ parse-srcset@^1.0.2: resolved "https://registry.yarnpkg.com/parse-srcset/-/parse-srcset-1.0.2.tgz#f2bd221f6cc970a938d88556abc589caaaa2bde1" integrity sha1-8r0iH2zJcKk42IVWq8WJyqqiveE= +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + integrity sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw== + 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" @@ -960,6 +1978,11 @@ pify@^5.0.0: resolved "https://registry.yarnpkg.com/pify/-/pify-5.0.0.tgz#1f5eca3f5e87ebec28cc6d54a0e4aaf00acc127f" integrity sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA== +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + integrity sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg== + postcss-calc@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.0.0.tgz#a05b87aacd132740a5db09462a3612453e5df90a" @@ -1246,6 +2269,11 @@ postcss@^8.3.11: picocolors "^1.0.0" source-map-js "^1.0.2" +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.series@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/promise.series/-/promise.series-0.2.0.tgz#2cc7ebe959fc3a6619c04ab4dbdc9e452d864bbd" @@ -1258,6 +2286,27 @@ randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" +readable-stream@~2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + regexparam@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/regexparam/-/regexparam-2.0.0.tgz#059476767d5f5f87f735fc7922d133fd1a118c8c" @@ -1268,6 +2317,31 @@ regexparam@^1.3.0: resolved "https://registry.yarnpkg.com/regexparam/-/regexparam-1.3.0.tgz#2fe42c93e32a40eff6235d635e0ffa344b92965f" integrity sha512-6IQpFBv6e5vz1QAqI+V4k8P2e/3gRrqfCJ9FI+O1FLQTO+Uz6RXZEZOPmTJ6hlGj7gkERzY5BRCv09whKP96/g== +relative@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/relative/-/relative-3.0.2.tgz#0dcd8ec54a5d35a3c15e104503d65375b5a5367f" + integrity sha512-Q5W2qeYtY9GbiR8z1yHNZ1DGhyjb4AnLEjt8iE6XfcC1QIu+FAtj3HQaO0wH28H1mX6cqNLvAqWhP402dxJGyA== + dependencies: + isobject "^2.0.0" + +remarkable@^1.6.2: + version "1.7.4" + resolved "https://registry.yarnpkg.com/remarkable/-/remarkable-1.7.4.tgz#19073cb960398c87a7d6546eaa5e50d2022fcd00" + integrity sha512-e6NKUXgX95whv7IgddywbeN/ItCkWbISmc2DiqHJb0wTrqZIexqdco5b8Z3XZoo/48IdNVKM9ZCvTPJ4F5uvhg== + dependencies: + argparse "^1.0.10" + autolinker "~0.28.0" + +repeat-element@^1.1.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9" + integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ== + +repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -1283,6 +2357,11 @@ resolve-from@^5.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + integrity sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg== + resolve@^1.17.0, resolve@^1.19.0: version "1.20.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" @@ -1291,6 +2370,11 @@ resolve@^1.17.0, resolve@^1.19.0: is-core-module "^2.2.0" path-parse "^1.0.6" +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== + rollup-plugin-json@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/rollup-plugin-json/-/rollup-plugin-json-4.0.0.tgz#a18da0a4b30bf5ca1ee76ddb1422afbb84ae2b9e" @@ -1381,7 +2465,7 @@ rollup@^2.44.0: optionalDependencies: fsevents "~2.3.2" -safe-buffer@^5.1.0: +safe-buffer@^5.1.0, 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== @@ -1391,6 +2475,13 @@ safe-identifier@^0.4.2: resolved "https://registry.yarnpkg.com/safe-identifier/-/safe-identifier-0.4.2.tgz#cf6bfca31c2897c588092d1750d30ef501d59fcb" integrity sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w== +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + integrity sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg== + dependencies: + ret "~0.1.10" + sanitize-html@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.7.0.tgz#e106205b468aca932e2f9baf241f24660d34e279" @@ -1408,6 +2499,11 @@ screenfull@^6.0.1: resolved "https://registry.yarnpkg.com/screenfull/-/screenfull-6.0.1.tgz#3b71e6f06b72d817a8d3be73c45ebe71fa8da1ce" integrity sha512-yzQW+j4zMUBQC51xxWaoDYjxOtl8Kn+xvue3p6v/fv2pIi1jH4AldgVLU8TBfFVgH2x3VXlf3+YiA/AYIPlaew== +self-closing-tags@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/self-closing-tags/-/self-closing-tags-1.0.1.tgz#6c5fa497994bb826b484216916371accee490a5d" + integrity sha512-7t6hNbYMxM+VHXTgJmxwgZgLGktuXtVVD5AivWzNTdJBM4DBjnDKDzkf2SrNjihaArpeJYNjxkELBu1evI4lQA== + serialize-javascript@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa" @@ -1415,6 +2511,16 @@ serialize-javascript@^4.0.0: dependencies: randombytes "^2.1.0" +set-value@^2.0.0, set-value@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" + integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + shortid@^2.2.15: version "2.2.16" resolved "https://registry.yarnpkg.com/shortid/-/shortid-2.2.16.tgz#b742b8f0cb96406fd391c76bfc18a67a57fe5608" @@ -1427,6 +2533,36 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^3.1.0" + socket.io-client@^4.5.1: version "4.5.1" resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.5.1.tgz#cab8da71976a300d3090414e28c2203a47884d84" @@ -1450,6 +2586,17 @@ source-map-js@^1.0.1, source-map-js@^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-resolve@^0.5.0: + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" + integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + source-map-support@~0.5.20: version "0.5.21" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" @@ -1458,6 +2605,16 @@ source-map-support@~0.5.20: buffer-from "^1.0.0" source-map "^0.6.0" +source-map-url@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" + integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== + +source-map@^0.5.6: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== + 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" @@ -1473,11 +2630,31 @@ sourcemap-codec@^1.4.4: resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== + dependencies: + extend-shallow "^3.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + stable@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + integrity sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g== + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + string-hash@^1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b" @@ -1492,6 +2669,13 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" @@ -1499,6 +2683,11 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" +striptags@^3.1.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/striptags/-/striptags-3.2.0.tgz#cc74a137db2de8b0b9a370006334161f7dd67052" + integrity sha512-g45ZOGzHDMe2bdYMdIvdAfCQkCTDMGBazSw1ypMowwGIee7ZQ5dU0rBJ8Jqgl+jAKIv4dbeE1jscZq9wid1Tkw== + style-inject@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/style-inject/-/style-inject-0.3.0.tgz#d21c477affec91811cc82355832a700d22bf8dd3" @@ -1533,13 +2722,18 @@ svelte-apexcharts@^1.0.2: dependencies: apexcharts "^3.19.2" -svelte-flatpickr@^3.1.0: +svelte-flatpickr@^3.1.0, svelte-flatpickr@^3.2.3: version "3.2.6" resolved "https://registry.yarnpkg.com/svelte-flatpickr/-/svelte-flatpickr-3.2.6.tgz#595a97b2f25a669e61fe743f90a10dce783bbd49" integrity sha512-0ePUyE9OjInYFqQwRKOxnFSu4dQX9+/rzFMynq2fKYXx406ZUThzSx72gebtjr0DoAQbsH2///BBZa5qk4qZXg== dependencies: flatpickr "^4.5.2" +svelte-portal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/svelte-portal/-/svelte-portal-1.0.0.tgz#36a47c5578b1a4d9b4dc60fa32a904640ec4cdd3" + integrity sha512-nHf+DS/jZ6jjnZSleBMSaZua9JlG5rZv9lOGKgJuaZStfevtjIlUJrkLc3vbV8QdBvPPVmvcjTlazAzfKu0v3Q== + svelte-spa-router@^3.0.5: version "3.2.0" resolved "https://registry.yarnpkg.com/svelte-spa-router/-/svelte-spa-router-3.2.0.tgz#fae3311d292451236cb57131262406cf312b15ee" @@ -1547,6 +2741,11 @@ svelte-spa-router@^3.0.5: dependencies: regexparam "2.0.0" +svelte@^3.46.2: + version "3.55.0" + resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.55.0.tgz#29cb958750a23e751309a6535ccd811fcabc9038" + integrity sha512-uGu2FVMlOuey4JoKHKrpZFkoYyj0VLjJdz47zX5+gVK5odxHM40RVhar9/iK2YFRVxvfg9FkhfVlR0sjeIrOiA== + svelte@^3.49.0: version "3.49.0" resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.49.0.tgz#5baee3c672306de1070c3b7888fc2204e36a4029" @@ -1630,16 +2829,112 @@ terser@^5.0.0: commander "^2.20.0" source-map-support "~0.5.20" +through2@^2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + timsort@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= -util-deprecate@^1.0.2: +to-gfm-code-block@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/to-gfm-code-block/-/to-gfm-code-block-0.1.1.tgz#25d045a5fae553189e9637b590900da732d8aa82" + integrity sha512-LQRZWyn8d5amUKnfR9A9Uu7x9ss7Re8peuWR2gkh1E+ildOfv2aF26JpuDg8JtvCduu5+hOrMIH+XstZtnagqg== + +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + integrity sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg== + dependencies: + kind-of "^3.0.2" + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + integrity sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg== + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + +typeof-article@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/typeof-article/-/typeof-article-0.1.1.tgz#9f07e733c3fbb646ffa9e61c08debacd460e06af" + integrity sha512-Vn42zdX3FhmUrzEmitX3iYyLb+Umwpmv8fkZRIknYh84lmdrwqZA5xYaoKiIj2Rc5i/5wcDrpUmZcbk1U51vTw== + dependencies: + kind-of "^3.1.0" + +typo-js@*: + version "1.2.2" + resolved "https://registry.yarnpkg.com/typo-js/-/typo-js-1.2.2.tgz#340484d81fe518e77c81a5a770162b14492f183b" + integrity sha512-C7pYBQK17EjSg8tVNY91KHdUt5Nf6FMJ+c3js076quPmBML57PmNMzAcIq/2kf/hSYtFABNDIYNYlJRl5BJhGw== + +uglify-js@^3.1.4: + version "3.17.4" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c" + integrity sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g== + +union-value@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" + integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^2.0.1" + +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + integrity sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ== + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + integrity sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg== + +use@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" + integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== + +util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== +vm2@^3.9.4: + version "3.9.13" + resolved "https://registry.yarnpkg.com/vm2/-/vm2-3.9.13.tgz#774a1a3d73b9b90b1aa45bcc5f25e349f2eef649" + integrity sha512-0rvxpB8P8Shm4wX2EKOiMp7H2zq+HUE/UwodY0pCZXs9IffIKZq6vUti5OgkVCTakKo9e/fgO4X1fkwfjWxE3Q== + dependencies: + acorn "^8.7.0" + acorn-walk "^8.2.0" + +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -1664,6 +2959,11 @@ xmlhttprequest-ssl@~2.0.0: resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67" integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A== +xtend@~4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + y18n@^5.0.5: version "5.0.8" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" @@ -1691,3 +2991,8 @@ yargs@^17.3.1: string-width "^4.2.3" y18n "^5.0.5" yargs-parser "^21.0.0" + +year@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/year/-/year-0.2.1.tgz#4083ae520a318b23ec86037f3000cb892bdf9bb0" + integrity sha512-9GnJUZ0QM4OgXuOzsKNzTJ5EOkums1Xc+3YQXp+Q+UxFjf7zLucp9dQ8QMIft0Szs1E1hUiXFim1OYfEKFq97w== diff --git a/packages/frontend-core/package.json b/packages/frontend-core/package.json index c150082ca5..0e36127e0c 100644 --- a/packages/frontend-core/package.json +++ b/packages/frontend-core/package.json @@ -1,12 +1,12 @@ { "name": "@budibase/frontend-core", - "version": "2.1.43-alpha.15", + "version": "2.2.12-alpha.4", "description": "Budibase frontend core libraries used in builder and client", "author": "Budibase", "license": "MPL-2.0", "svelte": "src/index.js", "dependencies": { - "@budibase/bbui": "2.1.43-alpha.15", + "@budibase/bbui": "2.2.12-alpha.4", "lodash": "^4.17.21", "svelte": "^3.46.2" } diff --git a/packages/frontend-core/src/api/app.js b/packages/frontend-core/src/api/app.js index 897e735ea1..7868eef063 100644 --- a/packages/frontend-core/src/api/app.js +++ b/packages/frontend-core/src/api/app.js @@ -22,11 +22,11 @@ export const buildAppEndpoints = API => ({ }, /** - * Deploys the current app. + * Publishes the current app. */ - deployAppChanges: async () => { + publishAppChanges: async appId => { return await API.post({ - url: "/api/deploy", + url: `/api/applications/${appId}/publish`, }) }, @@ -98,8 +98,8 @@ export const buildAppEndpoints = API => ({ * @param appId the production ID of the app to unpublish */ unpublishApp: async appId => { - return await API.delete({ - url: `/api/applications/${appId}?unpublish=1`, + return await API.post({ + url: `/api/applications/${appId}/unpublish`, }) }, diff --git a/packages/frontend-core/src/api/views.js b/packages/frontend-core/src/api/views.js index 237a66bc13..1d710c5171 100644 --- a/packages/frontend-core/src/api/views.js +++ b/packages/frontend-core/src/api/views.js @@ -16,8 +16,8 @@ export const buildViewEndpoints = API => ({ params.set("group", groupBy) } const QUERY_VIEW_URL = field - ? `/api/views/${name}?${params}` - : `/api/views/${name}` + ? `/api/views/${encodeURIComponent(name)}?${params}` + : `/api/views/${encodeURIComponent(name)}` return await API.get({ url: QUERY_VIEW_URL }) }, @@ -53,7 +53,7 @@ export const buildViewEndpoints = API => ({ */ deleteView: async viewName => { return await API.delete({ - url: `/api/views/${viewName}`, + url: `/api/views/${encodeURIComponent(viewName)}`, }) }, }) diff --git a/packages/frontend-core/src/utils/lucene.js b/packages/frontend-core/src/utils/lucene.js index 774ddbd834..427d8f8b97 100644 --- a/packages/frontend-core/src/utils/lucene.js +++ b/packages/frontend-core/src/utils/lucene.js @@ -7,7 +7,7 @@ const HBS_REGEX = /{{([^{].*?)}}/g * Returns the valid operator options for a certain data type * @param type the data type */ -export const getValidOperatorsForType = type => { +export const getValidOperatorsForType = (type, field, datasource) => { const Op = OperatorOptions const stringOps = [ Op.Equals, @@ -27,24 +27,37 @@ export const getValidOperatorsForType = type => { Op.NotEmpty, Op.In, ] + let ops = [] if (type === "string") { - return stringOps + ops = stringOps } else if (type === "number") { - return numOps + ops = numOps } else if (type === "options") { - return [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty, Op.In] + ops = [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty, Op.In] } else if (type === "array") { - return [Op.Contains, Op.NotContains, Op.Empty, Op.NotEmpty, Op.ContainsAny] + ops = [Op.Contains, Op.NotContains, Op.Empty, Op.NotEmpty, Op.ContainsAny] } else if (type === "boolean") { - return [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty] + ops = [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty] } else if (type === "longform") { - return stringOps + ops = stringOps } else if (type === "datetime") { - return numOps + ops = numOps } else if (type === "formula") { - return stringOps.concat([Op.MoreThan, Op.LessThan]) + ops = stringOps.concat([Op.MoreThan, Op.LessThan]) } - return [] + + // Filter out "like" for internal tables + const externalTable = datasource?.tableId?.includes("datasource_plus") + if (datasource?.type === "table" && !externalTable) { + ops = ops.filter(x => x !== Op.Like) + } + + // Only allow equal/not equal for _id in SQL tables + if (field === "_id" && externalTable) { + ops = [Op.Equals, Op.NotEquals] + } + + return ops } /** diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 5ff9f66f1f..189846e858 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/sdk", - "version": "2.1.43-alpha.15", + "version": "2.2.12-alpha.4", "description": "Budibase Public API SDK", "author": "Budibase", "license": "MPL-2.0", diff --git a/packages/server/__mocks__/aws-sdk.ts b/packages/server/__mocks__/aws-sdk.ts index b0da0e97b6..2efbd303f2 100644 --- a/packages/server/__mocks__/aws-sdk.ts +++ b/packages/server/__mocks__/aws-sdk.ts @@ -51,6 +51,25 @@ module AwsMock { Contents: {}, }) ) + + // @ts-ignore + this.getSignedUrl = (operation, params) => { + return `http://test.com/${params.Bucket}/${params.Key}` + } + + // @ts-ignore + this.headBucket = jest.fn( + response({ + Contents: {}, + }) + ) + + // @ts-ignore + this.upload = jest.fn( + response({ + Contents: {}, + }) + ) } aws.DynamoDB = { DocumentClient } diff --git a/packages/server/__mocks__/node-fetch.ts b/packages/server/__mocks__/node-fetch.ts index c3d6b623dd..44d1e54a32 100644 --- a/packages/server/__mocks__/node-fetch.ts +++ b/packages/server/__mocks__/node-fetch.ts @@ -1,3 +1,4 @@ +import fs from "fs" module FetchMock { const fetch = jest.requireActual("node-fetch") let failCount = 0 @@ -92,6 +93,83 @@ module FetchMock { value: '', }) + } else if ( + url === "https://api.github.com/repos/my-repo/budibase-comment-box" + ) { + return Promise.resolve({ + json: () => { + return { + name: "budibase-comment-box", + releases_url: + "https://api.github.com/repos/my-repo/budibase-comment-box{/id}", + } + }, + }) + } else if ( + url === "https://api.github.com/repos/my-repo/budibase-comment-box/latest" + ) { + return Promise.resolve({ + json: () => { + return { + assets: [ + { + content_type: "application/gzip", + browser_download_url: + "https://github.com/my-repo/budibase-comment-box/releases/download/v1.0.2/comment-box-1.0.2.tar.gz", + }, + ], + } + }, + }) + } else if ( + url === + "https://github.com/my-repo/budibase-comment-box/releases/download/v1.0.2/comment-box-1.0.2.tar.gz" + ) { + return Promise.resolve({ + body: fs.createReadStream( + "src/api/routes/tests/data/comment-box-1.0.2.tar.gz" + ), + ok: true, + }) + } else if (url === "https://www.npmjs.com/package/budibase-component") { + return Promise.resolve({ + status: 200, + json: () => { + return { + name: "budibase-component", + "dist-tags": { + latest: "1.0.0", + }, + versions: { + "1.0.0": { + dist: { + tarball: + "https://registry.npmjs.org/budibase-component/-/budibase-component-1.0.2.tgz", + }, + }, + }, + } + }, + }) + } else if ( + url === + "https://registry.npmjs.org/budibase-component/-/budibase-component-1.0.2.tgz" + ) { + return Promise.resolve({ + body: fs.createReadStream( + "src/api/routes/tests/data/budibase-component-1.0.2.tgz" + ), + ok: true, + }) + } else if ( + url === "https://www.someurl.com/comment-box/comment-box-1.0.2.tar.gz" + ) { + return Promise.resolve({ + body: fs.createReadStream( + "src/api/routes/tests/data/comment-box-1.0.2.tar.gz" + ), + ok: true, + }) } else if (url.includes("failonce.com")) { failCount++ if (failCount === 1) { diff --git a/packages/server/package.json b/packages/server/package.json index a80d652355..3e6272d015 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/server", "email": "hi@budibase.com", - "version": "2.1.43-alpha.15", + "version": "2.2.12-alpha.4", "description": "Budibase Web Server", "main": "src/index.ts", "repository": { @@ -43,11 +43,11 @@ "license": "GPL-3.0", "dependencies": { "@apidevtools/swagger-parser": "10.0.3", - "@budibase/backend-core": "2.1.43-alpha.15", - "@budibase/client": "2.1.43-alpha.15", - "@budibase/pro": "2.1.43-alpha.15", - "@budibase/string-templates": "2.1.43-alpha.15", - "@budibase/types": "2.1.43-alpha.15", + "@budibase/backend-core": "2.2.12-alpha.4", + "@budibase/client": "2.2.12-alpha.4", + "@budibase/pro": "2.2.12-alpha.4", + "@budibase/string-templates": "2.2.12-alpha.4", + "@budibase/types": "2.2.12-alpha.4", "@bull-board/api": "3.7.0", "@bull-board/koa": "3.9.4", "@elastic/elasticsearch": "7.10.0", @@ -130,13 +130,15 @@ "@types/ioredis": "4.28.10", "@types/jest": "27.5.1", "@types/koa": "2.13.4", - "@types/koa__router": "8.0.11", + "@types/koa__router": "8.0.8", "@types/lodash": "4.14.180", "@types/node": "14.18.20", "@types/node-fetch": "2.6.1", "@types/oracledb": "5.2.2", "@types/pouchdb": "6.4.0", "@types/redis": "4.0.11", + "@types/server-destroy": "1.0.1", + "@types/tar": "6.1.3", "@typescript-eslint/parser": "5.45.0", "apidoc": "0.50.4", "babel-jest": "27.5.1", diff --git a/packages/server/scripts/dev/manage.js b/packages/server/scripts/dev/manage.js index e06c6308f8..80f21cd043 100644 --- a/packages/server/scripts/dev/manage.js +++ b/packages/server/scripts/dev/manage.js @@ -2,12 +2,6 @@ const compose = require("docker-compose") const path = require("path") const fs = require("fs") -const isWsl = require("is-wsl") -const { processStringSync } = require("@budibase/string-templates") - -function isLinux() { - return !isWsl && process.platform !== "darwin" && process.platform !== "win32" -} // This script wraps docker-compose allowing you to manage your dev infrastructure with simple commands. const CONFIG = { @@ -23,16 +17,6 @@ const Commands = { } async function init() { - // generate nginx file, always do this incase it has changed - const hostingPath = path.join(process.cwd(), "..", "..", "hosting") - const nginxHbsPath = path.join(hostingPath, "nginx.dev.conf.hbs") - const nginxOutputPath = path.join(hostingPath, ".generated-nginx.dev.conf") - const contents = fs.readFileSync(nginxHbsPath, "utf8") - const config = { - address: isLinux() ? "172.17.0.1" : "host.docker.internal", - } - fs.writeFileSync(nginxOutputPath, processStringSync(contents, config)) - const envFilePath = path.join(process.cwd(), ".env") if (!fs.existsSync(envFilePath)) { const envFileJson = { diff --git a/packages/server/specs/openapi.json b/packages/server/specs/openapi.json index 62f59e9113..9a0d69e352 100644 --- a/packages/server/specs/openapi.json +++ b/packages/server/specs/openapi.json @@ -567,6 +567,40 @@ "data" ] }, + "deploymentOutput": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "_id": { + "description": "The ID of the app.", + "type": "string" + }, + "status": { + "description": "Status of the deployment, whether it succeeded or failed", + "type": "string", + "enum": [ + "SUCCESS", + "FAILURE" + ] + }, + "appUrl": { + "description": "The URL of the published app", + "type": "string" + } + }, + "required": [ + "_id", + "status", + "appUrl" + ] + } + }, + "required": [ + "data" + ] + }, "row": { "description": "The row to be created/updated, based on the table schema.", "type": "object", @@ -1933,6 +1967,56 @@ } } }, + "/applications/{appId}/unpublish": { + "post": { + "operationId": "unpublish", + "summary": "Unpublish an application", + "tags": [ + "applications" + ], + "parameters": [ + { + "$ref": "#/components/parameters/appIdUrl" + } + ], + "responses": { + "204": { + "description": "The app was published successfully." + } + } + } + }, + "/applications/{appId}/publish": { + "post": { + "operationId": "publish", + "summary": "Unpublish an application", + "tags": [ + "applications" + ], + "parameters": [ + { + "$ref": "#/components/parameters/appIdUrl" + } + ], + "responses": { + "200": { + "description": "Returns the deployment object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/deploymentOutput" + }, + "examples": { + "deployment": { + "$ref": "#/components/examples/deploymentOutput" + } + } + } + } + } + } + } + }, "/applications/search": { "post": { "operationId": "search", diff --git a/packages/server/specs/openapi.yaml b/packages/server/specs/openapi.yaml index 25069f40a4..69e44d881c 100644 --- a/packages/server/specs/openapi.yaml +++ b/packages/server/specs/openapi.yaml @@ -411,6 +411,30 @@ components: - version required: - data + deploymentOutput: + type: object + properties: + data: + type: object + properties: + _id: + description: The ID of the app. + type: string + status: + description: Status of the deployment, whether it succeeded or failed + type: string + enum: + - SUCCESS + - FAILURE + appUrl: + description: The URL of the published app + type: string + required: + - _id + - status + - appUrl + required: + - data row: description: The row to be created/updated, based on the table schema. type: object @@ -1453,6 +1477,35 @@ paths: examples: application: $ref: "#/components/examples/application" + "/applications/{appId}/unpublish": + post: + operationId: unpublish + summary: Unpublish an application + tags: + - applications + parameters: + - $ref: "#/components/parameters/appIdUrl" + responses: + "204": + description: The app was published successfully. + "/applications/{appId}/publish": + post: + operationId: publish + summary: Unpublish an application + tags: + - applications + parameters: + - $ref: "#/components/parameters/appIdUrl" + responses: + "200": + description: Returns the deployment object. + content: + application/json: + schema: + $ref: "#/components/schemas/deploymentOutput" + examples: + deployment: + $ref: "#/components/examples/deploymentOutput" /applications/search: post: operationId: search diff --git a/packages/server/specs/resources/application.js b/packages/server/specs/resources/application.js index 089738439a..6e73817898 100644 --- a/packages/server/specs/resources/application.js +++ b/packages/server/specs/resources/application.js @@ -80,6 +80,22 @@ const applicationOutputSchema = object( } ) +const deploymentOutputSchema = object({ + _id: { + description: "The ID of the app.", + type: "string", + }, + status: { + description: "Status of the deployment, whether it succeeded or failed", + type: "string", + enum: ["SUCCESS", "FAILURE"], + }, + appUrl: { + description: "The URL of the published app", + type: "string", + }, +}) + module.exports = new Resource() .setExamples({ application: { @@ -104,4 +120,7 @@ module.exports = new Resource() items: applicationOutputSchema, }, }), + deploymentOutput: object({ + data: deploymentOutputSchema, + }), }) diff --git a/packages/server/specs/resources/query.js b/packages/server/specs/resources/query.js index 1442e46a04..10544ee7eb 100644 --- a/packages/server/specs/resources/query.js +++ b/packages/server/specs/resources/query.js @@ -1,6 +1,6 @@ const Resource = require("./utils/Resource") const { object } = require("./utils") -const { BaseQueryVerbs } = require("../../src/constants") +const { BaseQueryVerbs } = require("../../dist/constants") const query = { _id: "query_datasource_plus_4d8be0c506b9465daf4bf84d890fdab6_454854487c574d45bc4029b1e153219e", diff --git a/packages/server/specs/resources/table.js b/packages/server/specs/resources/table.js index 523a3a9dfd..9bc57daf42 100644 --- a/packages/server/specs/resources/table.js +++ b/packages/server/specs/resources/table.js @@ -2,7 +2,7 @@ const { FieldTypes, RelationshipTypes, FormulaTypes, -} = require("../../src/constants") +} = require("../../dist/constants") const { object } = require("./utils") const Resource = require("./utils/Resource") diff --git a/packages/server/src/api/controllers/application.ts b/packages/server/src/api/controllers/application.ts index 7cb48e53df..06e7dc8a57 100644 --- a/packages/server/src/api/controllers/application.ts +++ b/packages/server/src/api/controllers/application.ts @@ -23,21 +23,18 @@ import { errors, events, migrations, + objectStore, } from "@budibase/backend-core" import { USERS_TABLE_SCHEMA } from "../../constants" import { buildDefaultDocs } from "../../db/defaultData/datasource_bb_default" import { removeAppFromUserRoles } from "../../utilities/workerRequests" -import { - clientLibraryPath, - stringToReadStream, - isQsTrue, -} from "../../utilities" +import { stringToReadStream, isQsTrue } from "../../utilities" import { getLocksById } from "../../utilities/redis" import { updateClientLibrary, backupClientLibrary, revertClientLibrary, -} from "../../utilities/fileSystem/clientLibrary" +} from "../../utilities/fileSystem" import { cleanupAutomations } from "../../automations/utils" import { checkAppMetadata } from "../../automations/logging" import { getUniqueRows } from "../../utilities/usageQuota/rows" @@ -49,9 +46,9 @@ import { MigrationType, BBContext, Database, + UserCtx, } from "@budibase/types" import { BASE_LAYOUT_PROP_IDS } from "../../constants/layouts" -import { enrichPluginURLs } from "../../utilities/plugins" import sdk from "../../sdk" // utility function, need to do away with this @@ -204,27 +201,34 @@ export async function fetchAppDefinition(ctx: BBContext) { } } -export async function fetchAppPackage(ctx: BBContext) { +export async function fetchAppPackage(ctx: UserCtx) { const db = context.getAppDB() let application = await db.get(DocumentType.APP_METADATA) const layouts = await getLayouts() let screens = await getScreens() // Enrich plugin URLs - application.usedPlugins = enrichPluginURLs(application.usedPlugins) + application.usedPlugins = objectStore.enrichPluginURLs( + application.usedPlugins + ) // Only filter screens if the user is not a builder - if (!(ctx.user?.builder && ctx.user.builder.global)) { + if (!(ctx.user.builder && ctx.user.builder.global)) { const userRoleId = getUserRoleId(ctx) const accessController = new roles.AccessController() screens = await accessController.checkScreensAccess(screens, userRoleId) } + const clientLibPath = objectStore.clientLibraryUrl( + ctx.params.appId, + application.version + ) + ctx.body = { application, screens, layouts, - clientLibPath: clientLibraryPath(ctx.params.appId, application.version), + clientLibPath, } } @@ -370,7 +374,7 @@ async function appPostCreate(ctx: BBContext, app: App) { if (err.code && err.code === errors.codes.USAGE_LIMIT_EXCEEDED) { // this import resulted in row usage exceeding the quota // delete the app - // skip pre- and post-steps as no rows have been added to quotas yet + // skip pre and post-steps as no rows have been added to quotas yet ctx.params.appId = app.appId await destroyApp(ctx) } @@ -460,41 +464,47 @@ export async function revertClient(ctx: BBContext) { ctx.body = app } -async function destroyApp(ctx: BBContext) { +const unpublishApp = async (ctx: any) => { let appId = ctx.params.appId - let isUnpublish = ctx.query && ctx.query.unpublish + appId = dbCore.getProdAppID(appId) - if (isUnpublish) { - appId = dbCore.getProdAppID(appId) - const devAppId = dbCore.getDevAppID(appId) - // sync before removing the published app - await sdk.applications.syncApp(devAppId) - } - - const db = isUnpublish ? context.getProdAppDB() : context.getAppDB() - const app = await db.get(DocumentType.APP_METADATA) + const db = context.getProdAppDB() const result = await db.destroy() - if (isUnpublish) { - await events.app.unpublished(app) - } else { - await quotas.removeApp() - await events.app.deleted(app) + await events.app.unpublished({ appId } as App) + + // automations only in production + await cleanupAutomations(appId) + + await cache.app.invalidateAppMetadata(appId) + return result +} + +async function destroyApp(ctx: BBContext) { + let appId = ctx.params.appId + appId = dbCore.getProdAppID(appId) + const devAppId = dbCore.getDevAppID(appId) + + // check if we need to unpublish first + if (await dbCore.dbExists(appId)) { + // app is deployed, run through unpublish flow + await sdk.applications.syncApp(devAppId) + await unpublishApp(ctx) } - /* istanbul ignore next */ - if (!env.isTest() && !isUnpublish) { + const db = dbCore.getDB(devAppId) + // standard app deletion flow + const app = await db.get(DocumentType.APP_METADATA) + const result = await db.destroy() + await quotas.removeApp() + await events.app.deleted(app) + + if (!env.isTest()) { await deleteApp(appId) } - // automations only in production - if (isUnpublish) { - await cleanupAutomations(appId) - } - // remove app role when the dev app is deleted (no trace of app anymore) - else { - await removeAppFromUserRoles(ctx, appId) - } - await cache.app.invalidateAppMetadata(appId) + + await removeAppFromUserRoles(ctx, appId) + await cache.app.invalidateAppMetadata(devAppId) return result } @@ -519,6 +529,21 @@ export async function destroy(ctx: BBContext) { ctx.body = result } +export const unpublish = async (ctx: BBContext) => { + const prodAppId = dbCore.getProdAppID(ctx.params.appId) + const dbExists = await dbCore.dbExists(prodAppId) + + // check app has been published + if (!dbExists) { + return ctx.throw(400, "App has not been published.") + } + + await preDestroyApp(ctx) + await unpublishApp(ctx) + await postDestroyApp(ctx) + ctx.status = 204 +} + export async function sync(ctx: BBContext) { const appId = ctx.params.appId try { diff --git a/packages/server/src/api/controllers/auth.ts b/packages/server/src/api/controllers/auth.ts index c28b99529b..15b99bf2f8 100644 --- a/packages/server/src/api/controllers/auth.ts +++ b/packages/server/src/api/controllers/auth.ts @@ -3,7 +3,7 @@ import { InternalTables } from "../../db/utils" import { getFullUser } from "../../utilities/users" import { roles, context } from "@budibase/backend-core" import { groups } from "@budibase/pro" -import { ContextUser, User, Row } from "@budibase/types" +import { ContextUser, User, Row, UserCtx } from "@budibase/types" const PUBLIC_ROLE = roles.BUILTIN_ROLE_IDS.PUBLIC @@ -16,7 +16,7 @@ const addSessionAttributesToUser = (ctx: any) => { } } -export async function fetchSelf(ctx: any) { +export async function fetchSelf(ctx: UserCtx) { let userId = ctx.user.userId || ctx.user._id /* istanbul ignore next */ if (!userId || !ctx.isAuthenticated) { diff --git a/packages/server/src/api/controllers/cloud.ts b/packages/server/src/api/controllers/cloud.ts index f8f7a27d00..be37bef641 100644 --- a/packages/server/src/api/controllers/cloud.ts +++ b/packages/server/src/api/controllers/cloud.ts @@ -5,8 +5,8 @@ import { stringToReadStream } from "../../utilities" import { getDocParams, DocumentType, isDevAppID } from "../../db/utils" import { create } from "./application" import { join } from "path" -import { App, BBContext, Database } from "@budibase/types" import sdk from "../../sdk" +import { App, Ctx, Database } from "@budibase/types" async function createApp(appName: string, appDirectory: string) { const ctx = { @@ -35,7 +35,7 @@ async function getAllDocType(db: Database, docType: string) { return response.rows.map(row => row.doc) } -export async function exportApps(ctx: BBContext) { +export async function exportApps(ctx: Ctx) { if (env.SELF_HOSTED || !env.MULTI_TENANCY) { ctx.throw(400, "Exporting only allowed in multi-tenant cloud environments.") } @@ -65,13 +65,13 @@ async function checkHasBeenImported() { return apps.length !== 0 } -export async function hasBeenImported(ctx: BBContext) { +export async function hasBeenImported(ctx: Ctx) { ctx.body = { imported: await checkHasBeenImported(), } } -export async function importApps(ctx: BBContext) { +export async function importApps(ctx: Ctx) { if (!env.SELF_HOSTED || env.MULTI_TENANCY) { ctx.throw(400, "Importing only allowed in self hosted environments.") } @@ -82,12 +82,16 @@ export async function importApps(ctx: BBContext) { "Import file is required and environment must be fresh to import apps." ) } - if (ctx.request.files.importFile.type !== "application/gzip") { + const file = ctx.request.files.importFile as any + if (Array.isArray(file)) { + ctx.throw(400, "Single file is required") + } + if (file.type !== "application/gzip") { ctx.throw(400, "Import file must be a gzipped tarball.") } // initially get all the app databases out of the tarball - const tmpPath = sdk.backups.untarFile(ctx.request.files.importFile) + const tmpPath = sdk.backups.untarFile(file) const globalDbImport = sdk.backups.getGlobalDBFile(tmpPath) const appNames = sdk.backups.getListOfAppsInMulti(tmpPath) diff --git a/packages/server/src/api/controllers/deploy/Deployment.ts b/packages/server/src/api/controllers/deploy/Deployment.ts index c15cffbcd8..611c82f28b 100644 --- a/packages/server/src/api/controllers/deploy/Deployment.ts +++ b/packages/server/src/api/controllers/deploy/Deployment.ts @@ -9,6 +9,7 @@ export default class Deployment { verification: any status?: string err?: any + appUrl?: string constructor(id = null) { this._id = id || newid() diff --git a/packages/server/src/api/controllers/deploy/index.ts b/packages/server/src/api/controllers/deploy/index.ts index 209aedfb62..60239d2c4c 100644 --- a/packages/server/src/api/controllers/deploy/index.ts +++ b/packages/server/src/api/controllers/deploy/index.ts @@ -94,7 +94,44 @@ async function initDeployedApp(prodAppId: any) { }) } -async function deployApp(deployment: any, userId: string) { +export async function fetchDeployments(ctx: any) { + try { + const db = context.getAppDB() + const deploymentDoc = await db.get(DocumentType.DEPLOYMENTS) + const { updated, deployments } = await checkAllDeployments(deploymentDoc) + if (updated) { + await db.put(deployments) + } + ctx.body = Object.values(deployments.history).reverse() + } catch (err) { + ctx.body = [] + } +} + +export async function deploymentProgress(ctx: any) { + try { + const db = context.getAppDB() + const deploymentDoc = await db.get(DocumentType.DEPLOYMENTS) + ctx.body = deploymentDoc[ctx.params.deploymentId] + } catch (err) { + ctx.throw( + 500, + `Error fetching data for deployment ${ctx.params.deploymentId}` + ) + } +} + +export const publishApp = async function (ctx: any) { + let deployment = new Deployment() + console.log("Deployment object created") + deployment.setStatus(DeploymentStatus.PENDING) + console.log("Deployment object set to pending") + deployment = await storeDeploymentHistory(deployment) + console.log("Stored deployment history") + + console.log("Deploying app...") + + let app let replication try { const appId = context.getAppId()! @@ -108,7 +145,7 @@ async function deployApp(deployment: any, userId: string) { productionAppId, AppBackupTrigger.PUBLISH, { - createdBy: userId, + createdBy: ctx.user._id, } ) } @@ -147,7 +184,7 @@ async function deployApp(deployment: any, userId: string) { console.log("Deployed app initialised, setting deployment to successful") deployment.setStatus(DeploymentStatus.SUCCESS) await storeDeploymentHistory(deployment) - return appDoc + app = appDoc } catch (err: any) { deployment.setStatus(DeploymentStatus.FAILURE, err.message) await storeDeploymentHistory(deployment) @@ -160,62 +197,7 @@ async function deployApp(deployment: any, userId: string) { await replication.close() } } -} - -export async function fetchDeployments(ctx: any) { - try { - const db = context.getAppDB() - const deploymentDoc = await db.get(DocumentType.DEPLOYMENTS) - const { updated, deployments } = await checkAllDeployments(deploymentDoc) - if (updated) { - await db.put(deployments) - } - ctx.body = Object.values(deployments.history).reverse() - } catch (err) { - ctx.body = [] - } -} - -export async function deploymentProgress(ctx: any) { - try { - const db = context.getAppDB() - const deploymentDoc = await db.get(DocumentType.DEPLOYMENTS) - ctx.body = deploymentDoc[ctx.params.deploymentId] - } catch (err) { - ctx.throw( - 500, - `Error fetching data for deployment ${ctx.params.deploymentId}` - ) - } -} - -const isFirstDeploy = async () => { - try { - const db = context.getProdAppDB() - await db.get(DocumentType.APP_METADATA) - } catch (e: any) { - if (e.status === 404) { - return true - } - throw e - } - return false -} - -const _deployApp = async function (ctx: any) { - let deployment = new Deployment() - console.log("Deployment object created") - deployment.setStatus(DeploymentStatus.PENDING) - console.log("Deployment object set to pending") - deployment = await storeDeploymentHistory(deployment) - console.log("Stored deployment history") - - console.log("Deploying app...") - - let app = await deployApp(deployment, ctx.user._id) await events.app.published(app) ctx.body = deployment } - -export { _deployApp as deployApp } diff --git a/packages/server/src/api/controllers/plugin/index.ts b/packages/server/src/api/controllers/plugin/index.ts index 907fcf65a6..faecbc1fd8 100644 --- a/packages/server/src/api/controllers/plugin/index.ts +++ b/packages/server/src/api/controllers/plugin/index.ts @@ -1,10 +1,14 @@ import { npmUpload, urlUpload, githubUpload, fileUpload } from "./uploaders" -import { plugins as pluginCore, tenancy } from "@budibase/backend-core" -import { PluginType, FileType, PluginSource } from "@budibase/types" +import { + plugins as pluginCore, + db as dbCore, + tenancy, + objectStore, +} from "@budibase/backend-core" +import { PluginType, FileType, PluginSource, Plugin } from "@budibase/types" import env from "../../../environment" import { ClientAppSocket } from "../../../websocket" -import { db as dbCore } from "@budibase/backend-core" -import { plugins } from "@budibase/pro" +import { sdk as pro } from "@budibase/pro" export async function getPlugins(type?: PluginType) { const db = tenancy.getGlobalDB() @@ -13,9 +17,10 @@ export async function getPlugins(type?: PluginType) { include_docs: true, }) ) - const plugins = response.rows.map((row: any) => row.doc) + let plugins = response.rows.map((row: any) => row.doc) as Plugin[] + plugins = objectStore.enrichPluginURLs(plugins) if (type) { - return plugins.filter((plugin: any) => plugin.schema?.type === type) + return plugins.filter((plugin: Plugin) => plugin.schema?.type === type) } else { return plugins } @@ -84,7 +89,7 @@ export async function create(ctx: any) { ) } - const doc = await plugins.storePlugin(metadata, directory, source) + const doc = await pro.plugins.storePlugin(metadata, directory, source) ClientAppSocket.emit("plugins-update", { name, hash: doc.hash }) ctx.body = { @@ -107,7 +112,7 @@ export async function destroy(ctx: any) { const { pluginId } = ctx.params try { - await plugins.deletePlugin(pluginId) + await pro.plugins.deletePlugin(pluginId) ctx.body = { message: `Plugin ${ctx.params.pluginId} deleted.` } } catch (err: any) { @@ -127,7 +132,7 @@ export async function processUploadedPlugin( throw new Error("Only component plugins are supported outside of self-host") } - const doc = await plugins.storePlugin(metadata, directory, source) + const doc = await pro.plugins.storePlugin(metadata, directory, source) ClientAppSocket.emit("plugin-update", { name: doc.name, hash: doc.hash }) return doc } diff --git a/packages/server/src/api/controllers/public/applications.ts b/packages/server/src/api/controllers/public/applications.ts index c4d41fe5fb..c4041bedcf 100644 --- a/packages/server/src/api/controllers/public/applications.ts +++ b/packages/server/src/api/controllers/public/applications.ts @@ -1,6 +1,7 @@ import { db as dbCore, context } from "@budibase/backend-core" import { search as stringSearch, addRev } from "./utils" import * as controller from "../application" +import * as deployController from "../deploy" import { Application } from "../../../definitions/common" function fixAppID(app: Application, params: any) { @@ -74,10 +75,26 @@ export async function destroy(ctx: any, next: any) { }) } +export async function unpublish(ctx: any, next: any) { + await context.doInAppContext(ctx.params.appId, async () => { + await controller.unpublish(ctx) + await next() + }) +} + +export async function publish(ctx: any, next: any) { + await context.doInAppContext(ctx.params.appId, async () => { + await deployController.publishApp(ctx) + await next() + }) +} + export default { create, update, read, destroy, search, + publish, + unpublish, } diff --git a/packages/server/src/api/controllers/row/ExternalRequest.ts b/packages/server/src/api/controllers/row/ExternalRequest.ts index a343553fc8..80ba4fd808 100644 --- a/packages/server/src/api/controllers/row/ExternalRequest.ts +++ b/packages/server/src/api/controllers/row/ExternalRequest.ts @@ -21,10 +21,10 @@ import { getDatasourceAndQuery } from "./utils" import { FieldTypes, RelationshipTypes } from "../../../constants" import { breakExternalTableId, isSQL } from "../../../integrations/utils" import { processObjectSync } from "@budibase/string-templates" -// @ts-ignore import { cloneDeep } from "lodash/fp" import { processFormulas, processDates } from "../../../utilities/rowProcessor" import { context } from "@budibase/backend-core" +import { removeKeyNumbering } from "./utils" export interface ManyRelationship { tableId?: string @@ -55,15 +55,21 @@ function buildFilters( let idCopy: undefined | string | any[] = cloneDeep(id) if (filters) { // need to map over the filters and make sure the _id field isn't present - for (let filter of Object.values(filters)) { - if (filter._id && primary) { - const parts = breakRowIdField(filter._id) - for (let field of primary) { - filter[field] = parts.shift() + let prefix = 1 + for (let operator of Object.values(filters)) { + for (let field of Object.keys(operator || {})) { + if (removeKeyNumbering(field) === "_id") { + if (primary) { + const parts = breakRowIdField(operator[field]) + for (let field of primary) { + operator[`${prefix}:${field}`] = parts.shift() + } + prefix++ + } + // make sure this field doesn't exist on any filter + delete operator[field] } } - // make sure this field doesn't exist on any filter - delete filter._id } } // there is no id, just use the user provided filters diff --git a/packages/server/src/api/controllers/row/internal.ts b/packages/server/src/api/controllers/row/internal.ts index ea3277cd59..9bfdff24ea 100644 --- a/packages/server/src/api/controllers/row/internal.ts +++ b/packages/server/src/api/controllers/row/internal.ts @@ -13,7 +13,7 @@ import { cleanupAttachments, } from "../../../utilities/rowProcessor" import { FieldTypes } from "../../../constants" -import { validate as rowValidate, findRow } from "./utils" +import * as utils from "./utils" import { fullSearch, paginatedSearch } from "./internalSearch" import { getGlobalUsersFromMetadata } from "../../../utilities/global" import * as inMemoryViews from "../../../db/inMemoryView" @@ -30,7 +30,8 @@ import { finaliseRow, updateRelatedFormula } from "./staticFormula" import * as exporters from "../view/exporters" import { apiFileReturn } from "../../../utilities/fileSystem" import { - BBContext, + Ctx, + UserCtx, Database, LinkDocumentValue, Row, @@ -69,7 +70,7 @@ async function getView(db: Database, viewName: string) { return viewInfo } -async function getRawTableData(ctx: BBContext, db: Database, tableId: string) { +async function getRawTableData(ctx: Ctx, db: Database, tableId: string) { let rows if (tableId === InternalTables.USER_METADATA) { await userController.fetchMetadata(ctx) @@ -85,7 +86,7 @@ async function getRawTableData(ctx: BBContext, db: Database, tableId: string) { return rows as Row[] } -export async function patch(ctx: BBContext) { +export async function patch(ctx: UserCtx) { const db = context.getAppDB() const inputs = ctx.request.body const tableId = inputs.tableId @@ -95,7 +96,7 @@ export async function patch(ctx: BBContext) { let dbTable = await db.get(tableId) oldRow = await outputProcessing( dbTable, - await findRow(ctx, tableId, inputs._id) + await utils.findRow(ctx, tableId, inputs._id) ) } catch (err) { if (isUserTable) { @@ -117,8 +118,8 @@ export async function patch(ctx: BBContext) { } // this returns the table and row incase they have been updated - let { table, row } = inputProcessing(ctx.user!, dbTable, combinedRow) - const validateResult = await rowValidate({ + let { table, row } = inputProcessing(ctx.user, dbTable, combinedRow) + const validateResult = await utils.validate({ row, table, }) @@ -150,7 +151,7 @@ export async function patch(ctx: BBContext) { }) } -export async function save(ctx: BBContext) { +export async function save(ctx: UserCtx) { const db = context.getAppDB() let inputs = ctx.request.body inputs.tableId = ctx.params.tableId @@ -161,8 +162,8 @@ export async function save(ctx: BBContext) { // this returns the table and row incase they have been updated const dbTable = await db.get(inputs.tableId) - let { table, row } = inputProcessing(ctx.user!, dbTable, inputs) - const validateResult = await rowValidate({ + let { table, row } = inputProcessing(ctx.user, dbTable, inputs) + const validateResult = await utils.validate({ row, table, }) @@ -185,8 +186,8 @@ export async function save(ctx: BBContext) { }) } -export async function fetchView(ctx: BBContext) { - const viewName = ctx.params.viewName +export async function fetchView(ctx: Ctx) { + const viewName = decodeURIComponent(ctx.params.viewName) // if this is a table view being looked for just transfer to that if (viewName.startsWith(DocumentType.TABLE)) { @@ -252,7 +253,7 @@ export async function fetchView(ctx: BBContext) { return rows } -export async function fetch(ctx: BBContext) { +export async function fetch(ctx: Ctx) { const db = context.getAppDB() const tableId = ctx.params.tableId @@ -261,15 +262,15 @@ export async function fetch(ctx: BBContext) { return outputProcessing(table, rows) } -export async function find(ctx: BBContext) { +export async function find(ctx: Ctx) { const db = dbCore.getDB(ctx.appId) const table = await db.get(ctx.params.tableId) - let row = await findRow(ctx, ctx.params.tableId, ctx.params.rowId) + let row = await utils.findRow(ctx, ctx.params.tableId, ctx.params.rowId) row = await outputProcessing(table, row) return row } -export async function destroy(ctx: BBContext) { +export async function destroy(ctx: Ctx) { const db = context.getAppDB() const { _id } = ctx.request.body let row = await db.get(_id) @@ -305,7 +306,7 @@ export async function destroy(ctx: BBContext) { return { response, row } } -export async function bulkDestroy(ctx: BBContext) { +export async function bulkDestroy(ctx: Ctx) { const db = context.getAppDB() const tableId = ctx.params.tableId const table = await db.get(tableId) @@ -344,7 +345,7 @@ export async function bulkDestroy(ctx: BBContext) { return { response: { ok: true }, rows: processedRows } } -export async function search(ctx: BBContext) { +export async function search(ctx: Ctx) { // Fetch the whole table when running in cypress, as search doesn't work if (!env.COUCH_DB_URL && env.isCypress()) { return { rows: await fetch(ctx) } @@ -376,14 +377,14 @@ export async function search(ctx: BBContext) { return response } -export async function validate(ctx: BBContext) { - return rowValidate({ +export async function validate(ctx: Ctx) { + return utils.validate({ tableId: ctx.params.tableId, row: ctx.request.body, }) } -export async function exportRows(ctx: BBContext) { +export async function exportRows(ctx: Ctx) { const db = context.getAppDB() const table = await db.get(ctx.params.tableId) const rowIds = ctx.request.body.rows @@ -421,14 +422,14 @@ export async function exportRows(ctx: BBContext) { return apiFileReturn(exporter(headers, rows)) } -export async function fetchEnrichedRow(ctx: BBContext) { +export async function fetchEnrichedRow(ctx: Ctx) { const db = context.getAppDB() const tableId = ctx.params.tableId const rowId = ctx.params.rowId // need table to work out where links go in row let [table, row] = await Promise.all([ db.get(tableId), - findRow(ctx, tableId, rowId), + utils.findRow(ctx, tableId, rowId), ]) // get the link docs const linkVals = (await linkRows.getLinkDocuments({ diff --git a/packages/server/src/api/controllers/row/staticFormula.ts b/packages/server/src/api/controllers/row/staticFormula.ts index 6e43f5822f..47a5af8f5a 100644 --- a/packages/server/src/api/controllers/row/staticFormula.ts +++ b/packages/server/src/api/controllers/row/staticFormula.ts @@ -16,7 +16,10 @@ const { cloneDeep } = require("lodash/fp") * updated. * NOTE: this will only for affect static formulas. */ -export async function updateRelatedFormula(table: Table, enrichedRows: Row[]) { +export async function updateRelatedFormula( + table: Table, + enrichedRows: Row[] | Row +) { const db = context.getAppDB() // no formula to update, we're done if (!table.relatedFormula) { @@ -155,7 +158,7 @@ export async function finaliseRow( enrichedRow = await processFormulas(table, enrichedRow, { dynamic: false }) // this updates the related formulas in other rows based on the relations to this row if (updateFormula) { - await exports.updateRelatedFormula(table, enrichedRow) + await updateRelatedFormula(table, enrichedRow) } return { row: enrichedRow, table } } diff --git a/packages/server/src/api/controllers/row/utils.ts b/packages/server/src/api/controllers/row/utils.ts index c003a16266..60aa8a6b7a 100644 --- a/packages/server/src/api/controllers/row/utils.ts +++ b/packages/server/src/api/controllers/row/utils.ts @@ -7,6 +7,7 @@ import { BBContext, Row, Table } from "@budibase/types" export { removeKeyNumbering } from "../../../integrations/base/utils" const validateJs = require("validate.js") const { cloneDeep } = require("lodash/fp") +import { Ctx } from "@budibase/types" validateJs.extend(validateJs.validators.datetime, { parse: function (value: string) { @@ -25,7 +26,7 @@ export async function getDatasourceAndQuery(json: any) { return makeExternalQuery(datasource, json) } -export async function findRow(ctx: BBContext, tableId: string, rowId: string) { +export async function findRow(ctx: Ctx, tableId: string, rowId: string) { const db = context.getAppDB() let row // TODO remove special user case in future diff --git a/packages/server/src/api/controllers/static/index.ts b/packages/server/src/api/controllers/static/index.ts index 32f5e33325..1113a2a1be 100644 --- a/packages/server/src/api/controllers/static/index.ts +++ b/packages/server/src/api/controllers/static/index.ts @@ -1,11 +1,9 @@ -import { enrichPluginURLs } from "../../../utilities/plugins" - require("svelte/register") const send = require("koa-send") const { resolve, join } = require("../../../utilities/centralPath") const uuid = require("uuid") -const { ObjectStoreBuckets } = require("../../../constants") +import { ObjectStoreBuckets } from "../../../constants" const { processString } = require("@budibase/string-templates") const { loadHandlebarsFile, @@ -13,8 +11,6 @@ const { TOP_LEVEL_PATH, } = require("../../../utilities/fileSystem") const env = require("../../../environment") -const { clientLibraryPath } = require("../../../utilities") -const { attachmentsRelativeURL } = require("../../../utilities") const { DocumentType } = require("../../../db/utils") const { context, objectStore, utils } = require("@budibase/backend-core") const AWS = require("aws-sdk") @@ -33,7 +29,7 @@ async function prepareUpload({ s3Key, bucket, metadata, file }: any) { return { size: file.size, name: file.name, - url: attachmentsRelativeURL(response.Key), + url: objectStore.getAppFileUrl(s3Key), extension: [...file.name.split(".")].pop(), key: response.Key, } @@ -85,7 +81,7 @@ export const uploadFile = async function (ctx: any) { return prepareUpload({ file, - s3Key: `${ctx.appId}/attachments/${processedFileName}`, + s3Key: `${context.getProdAppId()}/attachments/${processedFileName}`, bucket: ObjectStoreBuckets.APPS, }) }) @@ -107,14 +103,14 @@ export const serveApp = async function (ctx: any) { if (!env.isJest()) { const App = require("./templates/BudibaseApp.svelte").default - const plugins = enrichPluginURLs(appInfo.usedPlugins) + const plugins = objectStore.enrichPluginURLs(appInfo.usedPlugins) const { head, html, css } = App.render({ metaImage: "https://res.cloudinary.com/daog6scxm/image/upload/v1666109324/meta-images/budibase-meta-image_uukc1m.png", title: appInfo.name, production: env.isProd(), appId, - clientLibPath: clientLibraryPath(appId, appInfo.version, ctx), + clientLibPath: objectStore.clientLibraryUrl(appId, appInfo.version), usedPlugins: plugins, }) @@ -139,7 +135,7 @@ export const serveBuilderPreview = async function (ctx: any) { let appId = context.getAppId() const previewHbs = loadHandlebarsFile(`${__dirname}/templates/preview.hbs`) ctx.body = await processString(previewHbs, { - clientLibPath: clientLibraryPath(appId, appInfo.version, ctx), + clientLibPath: objectStore.clientLibraryUrl(appId, appInfo.version), }) } else { // just return the app info for jest to assert on diff --git a/packages/server/src/api/controllers/view/index.ts b/packages/server/src/api/controllers/view/index.ts index 17c3ee301d..932823d172 100644 --- a/packages/server/src/api/controllers/view/index.ts +++ b/packages/server/src/api/controllers/view/index.ts @@ -113,7 +113,7 @@ async function handleViewEvents(existingView: View, newView: View) { export async function destroy(ctx: BBContext) { const db = context.getAppDB() - const viewName = decodeURI(ctx.params.viewName) + const viewName = decodeURIComponent(ctx.params.viewName) const view = await deleteView(viewName) const table = await db.get(view.meta.tableId) delete table.views[viewName] @@ -124,7 +124,7 @@ export async function destroy(ctx: BBContext) { } export async function exportView(ctx: BBContext) { - const viewName = decodeURI(ctx.query.view as string) + const viewName = decodeURIComponent(ctx.query.view as string) const view = await getView(viewName) const format = ctx.query.format as string diff --git a/packages/server/src/api/routes/application.ts b/packages/server/src/api/routes/application.ts index 486953ea8b..26b014c52f 100644 --- a/packages/server/src/api/routes/application.ts +++ b/packages/server/src/api/routes/application.ts @@ -1,5 +1,6 @@ import Router from "@koa/router" import * as controller from "../controllers/application" +import * as deploymentController from "../controllers/deploy" import authorized from "../../middleware/authorized" import { permissions } from "@budibase/backend-core" import { applicationValidator } from "./utils/validators" @@ -37,6 +38,16 @@ router authorized(permissions.BUILDER), controller.revertClient ) + .post( + "/api/applications/:appId/publish", + authorized(permissions.BUILDER), + deploymentController.publishApp + ) + .post( + "/api/applications/:appId/unpublish", + authorized(permissions.BUILDER), + controller.unpublish + ) .delete( "/api/applications/:appId", authorized(permissions.BUILDER), diff --git a/packages/server/src/api/routes/deploy.ts b/packages/server/src/api/routes/deploy.ts index b0e4960acb..30d33f4c97 100644 --- a/packages/server/src/api/routes/deploy.ts +++ b/packages/server/src/api/routes/deploy.ts @@ -16,6 +16,5 @@ router authorized(permissions.BUILDER), controller.deploymentProgress ) - .post("/api/deploy", authorized(permissions.BUILDER), controller.deployApp) export = router diff --git a/packages/server/src/api/routes/index.ts b/packages/server/src/api/routes/index.ts index 02a4900077..8d6b1754a7 100644 --- a/packages/server/src/api/routes/index.ts +++ b/packages/server/src/api/routes/index.ts @@ -26,13 +26,14 @@ import cloudRoutes from "./cloud" import migrationRoutes from "./migrations" import pluginRoutes from "./plugin" import Router from "@koa/router" -import { api } from "@budibase/pro" +import { api as pro } from "@budibase/pro" export { default as staticRoutes } from "./static" export { default as publicRoutes } from "./public" -const appBackupRoutes = api.appBackups -const scheduleRoutes = api.schedules +const appBackupRoutes = pro.appBackups +const scheduleRoutes = pro.schedules + export const mainRoutes: Router[] = [ appBackupRoutes, backupRoutes, diff --git a/packages/server/src/api/routes/public/applications.ts b/packages/server/src/api/routes/public/applications.ts index 93d86d2aed..935321cb7b 100644 --- a/packages/server/src/api/routes/public/applications.ts +++ b/packages/server/src/api/routes/public/applications.ts @@ -1,6 +1,7 @@ import controller from "../../controllers/public/applications" import Endpoint from "./utils/Endpoint" const { nameValidator, applicationValidator } = require("../utils/validators") +import { db } from "@budibase/backend-core" const read = [], write = [] @@ -94,6 +95,49 @@ write.push( */ write.push(new Endpoint("delete", "/applications/:appId", controller.destroy)) +/** + * @openapi + * /applications/{appId}/unpublish: + * post: + * operationId: unpublish + * summary: Unpublish an application + * tags: + * - applications + * parameters: + * - $ref: '#/components/parameters/appIdUrl' + * responses: + * 204: + * description: The app was published successfully. + */ +write.push( + new Endpoint("post", "/applications/:appId/unpublish", controller.unpublish) +) + +/** + * @openapi + * /applications/{appId}/publish: + * post: + * operationId: publish + * summary: Unpublish an application + * tags: + * - applications + * parameters: + * - $ref: '#/components/parameters/appIdUrl' + * responses: + * 200: + * description: Returns the deployment object. + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/deploymentOutput' + * examples: + * deployment: + * $ref: '#/components/examples/deploymentOutput' + */ +write.push( + new Endpoint("post", "/applications/:appId/publish", controller.publish) +) + /** * @openapi * /applications/{appId}: diff --git a/packages/server/src/api/routes/public/middleware/mapper.ts b/packages/server/src/api/routes/public/middleware/mapper.ts index aae7b1d1cf..138a5ac23f 100644 --- a/packages/server/src/api/routes/public/middleware/mapper.ts +++ b/packages/server/src/api/routes/public/middleware/mapper.ts @@ -54,9 +54,13 @@ function processQueries(ctx: any) { } export default async (ctx: any, next: any) => { + if (!ctx.body) { + return await next() + } let urlParts = ctx.url.split("/") urlParts = urlParts.slice(4, urlParts.length) let body = {} + switch (urlParts[0]) { case Resources.APPLICATION: body = processApplications(ctx) diff --git a/packages/server/src/api/routes/tests/__snapshots__/datasource.spec.js.snap b/packages/server/src/api/routes/tests/__snapshots__/datasource.spec.ts.snap similarity index 100% rename from packages/server/src/api/routes/tests/__snapshots__/datasource.spec.js.snap rename to packages/server/src/api/routes/tests/__snapshots__/datasource.spec.ts.snap diff --git a/packages/server/src/api/routes/tests/analytics.spec.js b/packages/server/src/api/routes/tests/analytics.spec.js index 51196943e1..f8b3a6763f 100644 --- a/packages/server/src/api/routes/tests/analytics.spec.js +++ b/packages/server/src/api/routes/tests/analytics.spec.js @@ -1,5 +1,5 @@ const setup = require("./utilities") -const { events, constants, db } = require("@budibase/backend-core") +const { events, constants } = require("@budibase/backend-core") describe("/static", () => { let request = setup.getRequest() diff --git a/packages/server/src/api/routes/tests/application.spec.js b/packages/server/src/api/routes/tests/application.spec.ts similarity index 69% rename from packages/server/src/api/routes/tests/application.spec.js rename to packages/server/src/api/routes/tests/application.spec.ts index 53b2f0fada..3177f776c2 100644 --- a/packages/server/src/api/routes/tests/application.spec.js +++ b/packages/server/src/api/routes/tests/application.spec.ts @@ -11,14 +11,11 @@ jest.mock("../../../utilities/redis", () => ({ checkDebounce: jest.fn(), shutdown: jest.fn(), })) - -const { - clearAllApps, - checkBuilderEndpoint, -} = require("./utilities/TestFunctions") -const setup = require("./utilities") -const { AppStatus } = require("../../../db/utils") -const { events } = require("@budibase/backend-core") +import { clearAllApps, checkBuilderEndpoint } from "./utilities/TestFunctions" +import * as setup from "./utilities" +import { AppStatus } from "../../../db/utils" +import { events } from "@budibase/backend-core" +import env from "../../../environment" describe("/applications", () => { let request = setup.getRequest() @@ -162,33 +159,30 @@ describe("/applications", () => { }) }) - describe("delete", () => { - it("should delete app", async () => { - await config.createApp("to-delete") + describe("publish", () => { + it("should publish app with dev app ID", async () => { const appId = config.getAppId() await request - .delete(`/api/applications/${appId}`) + .post(`/api/applications/${appId}/publish`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) - expect(events.app.deleted).toBeCalledTimes(1) + expect(events.app.published).toBeCalledTimes(1) }) - it("should unpublish app", async () => { - await config.createApp("to-unpublish") + it("should publish app with prod app ID", async () => { const appId = config.getProdAppId() await request - .delete(`/api/applications/${appId}?unpublish=true`) + .post(`/api/applications/${appId}/publish`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) - expect(events.app.unpublished).toBeCalledTimes(1) + expect(events.app.published).toBeCalledTimes(1) }) }) describe("manage client library version", () => { it("should be able to update the app client library version", async () => { - console.log(config.getAppId()) await request .post(`/api/applications/${config.getAppId()}/client/update`) .set(config.defaultHeaders()) @@ -196,6 +190,7 @@ describe("/applications", () => { .expect(200) expect(events.app.versionUpdated).toBeCalledTimes(1) }) + it("should be able to revert the app client library version", async () => { // We need to first update the version so that we can then revert await request @@ -234,4 +229,85 @@ describe("/applications", () => { expect(getRes.body.application.updatedAt).toBeDefined() }) }) + + describe("sync", () => { + it("app should sync correctly", async () => { + const res = await request + .post(`/api/applications/${config.getAppId()}/sync`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body.message).toEqual("App sync completed successfully.") + }) + + it("app should not sync if production", async () => { + const res = await request + .post(`/api/applications/app_123456/sync`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(400) + expect(res.body.message).toEqual( + "This action cannot be performed for production apps" + ) + }) + + it("app should not sync if sync is disabled", async () => { + env._set("DISABLE_AUTO_PROD_APP_SYNC", true) + const res = await request + .post(`/api/applications/${config.getAppId()}/sync`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body.message).toEqual( + "App sync disabled. You can reenable with the DISABLE_AUTO_PROD_APP_SYNC environment variable." + ) + env._set("DISABLE_AUTO_PROD_APP_SYNC", false) + }) + }) + + describe("unpublish", () => { + it("should unpublish app with dev app ID", async () => { + const appId = config.getAppId() + await request + .post(`/api/applications/${appId}/unpublish`) + .set(config.defaultHeaders()) + .expect(204) + expect(events.app.unpublished).toBeCalledTimes(1) + }) + + it("should unpublish app with prod app ID", async () => { + const appId = config.getProdAppId() + await request + .post(`/api/applications/${appId}/unpublish`) + .set(config.defaultHeaders()) + .expect(204) + expect(events.app.unpublished).toBeCalledTimes(1) + }) + }) + + describe("delete", () => { + it("should delete published app and dev apps with dev app ID", async () => { + await config.createApp("to-delete") + const appId = config.getAppId() + await request + .delete(`/api/applications/${appId}`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(events.app.deleted).toBeCalledTimes(1) + expect(events.app.unpublished).toBeCalledTimes(1) + }) + + it("should delete published app and dev app with prod app ID", async () => { + await config.createApp("to-delete") + const appId = config.getProdAppId() + await request + .delete(`/api/applications/${appId}`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(events.app.deleted).toBeCalledTimes(1) + expect(events.app.unpublished).toBeCalledTimes(1) + }) + }) }) diff --git a/packages/server/src/api/routes/tests/backup.spec.js b/packages/server/src/api/routes/tests/backup.spec.ts similarity index 61% rename from packages/server/src/api/routes/tests/backup.spec.js rename to packages/server/src/api/routes/tests/backup.spec.ts index 7131aca852..72ccb41598 100644 --- a/packages/server/src/api/routes/tests/backup.spec.js +++ b/packages/server/src/api/routes/tests/backup.spec.ts @@ -8,10 +8,10 @@ jest.mock("@budibase/backend-core", () => { } }) -const { checkBuilderEndpoint } = require("./utilities/TestFunctions") -const setup = require("./utilities") -const { events } = require("@budibase/backend-core") - +import * as setup from "./utilities" +import { events } from "@budibase/backend-core" +import sdk from "../../../sdk" +import { checkBuilderEndpoint } from "./utilities/TestFunctions" describe("/backups", () => { let request = setup.getRequest() let config = setup.getConfig() @@ -30,7 +30,7 @@ describe("/backups", () => { .expect(200) expect(res.text).toBeDefined() expect(res.headers["content-type"]).toEqual("application/gzip") - expect(events.app.exported.mock.calls.length).toBe(1) + expect(events.app.exported).toBeCalledTimes(1) }) it("should apply authorization to endpoint", async () => { @@ -41,4 +41,15 @@ describe("/backups", () => { }) }) }) -}) \ No newline at end of file + + describe("calculateBackupStats", () => { + it("should be able to calculate the backup statistics", async () => { + config.createAutomation() + config.createScreen() + let res = await sdk.backups.calculateBackupStats(config.getAppId()) + expect(res.automations).toEqual(1) + expect(res.datasources).toEqual(1) + expect(res.screens).toEqual(1) + }) + }) +}) diff --git a/packages/server/src/api/routes/tests/cloud.spec.ts b/packages/server/src/api/routes/tests/cloud.spec.ts new file mode 100644 index 0000000000..f52bf838ff --- /dev/null +++ b/packages/server/src/api/routes/tests/cloud.spec.ts @@ -0,0 +1,65 @@ +import { db as dbCore } from "@budibase/backend-core" +import { AppStatus } from "../../../db/utils" + +import * as setup from "./utilities" + +describe("/cloud", () => { + let request = setup.getRequest() + let config = setup.getConfig() + + afterAll(setup.afterAll) + + beforeEach(async () => { + await config.init() + }) + + afterEach(async () => { + // clear all mocks + jest.clearAllMocks() + }) + + describe("import", () => { + it("should be able to import apps", async () => { + // first we need to delete any existing apps on the system so it looks clean otherwise the + // import will not run + await request + .post( + `/api/applications/${dbCore.getProdAppID( + config.getAppId() + )}/unpublish` + ) + .set(config.defaultHeaders()) + .expect(204) + await request + .delete(`/api/applications/${config.getAppId()}`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + + // get a count of apps before the import + const preImportApps = await request + .get(`/api/applications?status=${AppStatus.ALL}`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + + // Perform the import + const res = await request + .post(`/api/cloud/import`) + .attach("importFile", "src/api/routes/tests/data/export-test.tar.gz") + .set(config.defaultHeaders()) + .expect(200) + expect(res.body.message).toEqual("Apps successfully imported.") + + // get a count of apps after the import + const postImportApps = await request + .get(`/api/applications?status=${AppStatus.ALL}`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + + // There are two apps in the file that was imported so check for this + expect(postImportApps.body.length).toEqual(2) + }) + }) +}) diff --git a/packages/server/src/api/routes/tests/data/budibase-component-1.0.1.tgz b/packages/server/src/api/routes/tests/data/budibase-component-1.0.1.tgz new file mode 100644 index 0000000000..8de0015a26 Binary files /dev/null and b/packages/server/src/api/routes/tests/data/budibase-component-1.0.1.tgz differ diff --git a/packages/server/src/api/routes/tests/data/comment-box-1.0.2.tar.gz b/packages/server/src/api/routes/tests/data/comment-box-1.0.2.tar.gz new file mode 100644 index 0000000000..f3b00f4e5d Binary files /dev/null and b/packages/server/src/api/routes/tests/data/comment-box-1.0.2.tar.gz differ diff --git a/packages/server/src/api/routes/tests/data/export-test.tar.gz b/packages/server/src/api/routes/tests/data/export-test.tar.gz new file mode 100644 index 0000000000..add05daba0 Binary files /dev/null and b/packages/server/src/api/routes/tests/data/export-test.tar.gz differ diff --git a/packages/server/src/api/routes/tests/datasource.spec.js b/packages/server/src/api/routes/tests/datasource.spec.ts similarity index 85% rename from packages/server/src/api/routes/tests/datasource.spec.js rename to packages/server/src/api/routes/tests/datasource.spec.ts index 520a17015c..66888023c4 100644 --- a/packages/server/src/api/routes/tests/datasource.spec.js +++ b/packages/server/src/api/routes/tests/datasource.spec.ts @@ -1,16 +1,16 @@ jest.mock("pg") +import * as setup from "./utilities" +import { checkBuilderEndpoint } from "./utilities/TestFunctions" +import { checkCacheForDynamicVariable } from "../../../threads/utils" +import { events } from "@budibase/backend-core" -let setup = require("./utilities") let { basicDatasource } = setup.structures -let { checkBuilderEndpoint } = require("./utilities/TestFunctions") const pg = require("pg") -const { checkCacheForDynamicVariable } = require("../../../threads/utils") -const { events } = require("@budibase/backend-core") describe("/datasources", () => { let request = setup.getRequest() let config = setup.getConfig() - let datasource + let datasource: any afterAll(setup.afterAll) @@ -26,7 +26,7 @@ describe("/datasources", () => { .post(`/api/datasources`) .send(basicDatasource()) .set(config.defaultHeaders()) - .expect('Content-Type', /json/) + .expect("Content-Type", /json/) .expect(200) expect(res.body.datasource.name).toEqual("Test") @@ -42,7 +42,7 @@ describe("/datasources", () => { .put(`/api/datasources/${datasource._id}`) .send(datasource) .set(config.defaultHeaders()) - .expect('Content-Type', /json/) + .expect("Content-Type", /json/) .expect(200) expect(res.body.datasource.name).toEqual("Updated Test") @@ -51,25 +51,34 @@ describe("/datasources", () => { }) describe("dynamic variables", () => { - async function preview(datasource, fields) { + async function preview( + datasource: any, + fields: { path: string; queryString: string } + ) { return config.previewQuery(request, config, datasource, fields) } it("should invalidate changed or removed variables", async () => { const { datasource, query } = await config.dynamicVariableDatasource() // preview once to cache variables - await preview(datasource, { path: "www.test.com", queryString: "test={{ variable3 }}" }) + await preview(datasource, { + path: "www.test.com", + queryString: "test={{ variable3 }}", + }) // check variables in cache - let contents = await checkCacheForDynamicVariable(query._id, "variable3") + let contents = await checkCacheForDynamicVariable( + query._id, + "variable3" + ) expect(contents.rows.length).toEqual(1) - + // update the datasource to remove the variables datasource.config.dynamicVariables = [] const res = await request .put(`/api/datasources/${datasource._id}`) .send(datasource) .set(config.defaultHeaders()) - .expect('Content-Type', /json/) + .expect("Content-Type", /json/) .expect(200) expect(res.body.errors).toBeUndefined() @@ -85,7 +94,7 @@ describe("/datasources", () => { const res = await request .get(`/api/datasources`) .set(config.defaultHeaders()) - .expect('Content-Type', /json/) + .expect("Content-Type", /json/) .expect(200) const datasources = res.body @@ -160,7 +169,7 @@ describe("/datasources", () => { const res = await request .get(`/api/datasources`) .set(config.defaultHeaders()) - .expect('Content-Type', /json/) + .expect("Content-Type", /json/) .expect(200) expect(res.body.length).toEqual(1) @@ -174,6 +183,5 @@ describe("/datasources", () => { url: `/api/datasources/${datasource._id}/${datasource._rev}`, }) }) - }) }) diff --git a/packages/server/src/api/routes/tests/deployment.spec.ts b/packages/server/src/api/routes/tests/deployment.spec.ts deleted file mode 100644 index bd705f9f59..0000000000 --- a/packages/server/src/api/routes/tests/deployment.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import * as setup from "./utilities" -import { events } from "@budibase/backend-core" - -describe("/deployments", () => { - let request = setup.getRequest() - let config = setup.getConfig() - - afterAll(setup.afterAll) - - beforeEach(async () => { - await config.init() - jest.clearAllMocks() - }) - - describe("deploy", () => { - it("should deploy the application", async () => { - await request - .post(`/api/deploy`) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) - expect((events.app.published as jest.Mock).mock.calls.length).toBe(1) - }) - }) -}) diff --git a/packages/server/src/api/routes/tests/permissions.spec.js b/packages/server/src/api/routes/tests/permissions.spec.js index 093f63913a..888eb1014c 100644 --- a/packages/server/src/api/routes/tests/permissions.spec.js +++ b/packages/server/src/api/routes/tests/permissions.spec.js @@ -92,7 +92,7 @@ describe("/permission", () => { describe("check public user allowed", () => { it("should be able to read the row", async () => { // replicate changes before checking permissions - await config.deploy() + await config.publish() const res = await request .get(`/api/${table._id}/rows`) diff --git a/packages/server/src/api/routes/tests/plugin.spec.ts b/packages/server/src/api/routes/tests/plugin.spec.ts new file mode 100644 index 0000000000..8e59a9392c --- /dev/null +++ b/packages/server/src/api/routes/tests/plugin.spec.ts @@ -0,0 +1,179 @@ +let mockObjectStore = jest.fn().mockImplementation(() => { + return [{ name: "test.js" }] +}) + +let deleteFolder = jest.fn().mockImplementation() +jest.mock("@budibase/backend-core", () => { + const core = jest.requireActual("@budibase/backend-core") + return { + ...core, + objectStore: { + ...core.objectStore, + upload: jest.fn(), + uploadDirectory: mockObjectStore, + deleteFolder: deleteFolder, + }, + } +}) + +import { events } from "@budibase/backend-core" +import * as setup from "./utilities" + +describe("/plugins", () => { + let request = setup.getRequest() + let config = setup.getConfig() + + afterAll(setup.afterAll) + + beforeEach(async () => { + await config.init() + jest.clearAllMocks() + }) + + const createPlugin = async (status?: number) => { + return request + .post(`/api/plugin/upload`) + .attach("file", "src/api/routes/tests/data/comment-box-1.0.2.tar.gz") + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(status ? status : 200) + } + + const getPlugins = async (status?: number) => { + return request + .get(`/api/plugin`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(status ? status : 200) + } + + describe("upload", () => { + it("should be able to upload a plugin", async () => { + let res = await createPlugin() + expect(res.body).toBeDefined() + expect(res.body.plugins).toBeDefined() + expect(res.body.plugins[0]._id).toEqual("plg_comment-box") + expect(events.plugin.imported).toHaveBeenCalledTimes(1) + }) + + it("should not be able to create a plugin if there is an error", async () => { + mockObjectStore.mockImplementationOnce(() => { + throw new Error() + }) + let res = await createPlugin(400) + expect(res.body.message).toEqual("Failed to import plugin: Error") + expect(events.plugin.imported).toHaveBeenCalledTimes(0) + }) + }) + + describe("fetch", () => { + it("should be able to fetch plugins", async () => { + await createPlugin() + const res = await getPlugins() + expect(res.body).toBeDefined() + expect(res.body[0]._id).toEqual("plg_comment-box") + }) + }) + + describe("destroy", () => { + it("should be able to delete a plugin", async () => { + await createPlugin() + const res = await request + .delete(`/api/plugin/plg_comment-box`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body).toBeDefined() + expect(res.body.message).toEqual("Plugin plg_comment-box deleted.") + + const plugins = await getPlugins() + expect(plugins.body).toBeDefined() + expect(plugins.body.length).toEqual(0) + expect(events.plugin.deleted).toHaveBeenCalledTimes(1) + }) + it("should handle an error deleting a plugin", async () => { + deleteFolder.mockImplementationOnce(() => { + throw new Error() + }) + + await createPlugin() + const res = await request + .delete(`/api/plugin/plg_comment-box`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(400) + + expect(res.body.message).toEqual("Failed to delete plugin: Error") + expect(events.plugin.deleted).toHaveBeenCalledTimes(0) + const plugins = await getPlugins() + expect(plugins.body).toBeDefined() + expect(plugins.body.length).toEqual(1) + }) + }) + + describe("github", () => { + const createGithubPlugin = async (status?: number, url?: string) => { + return await request + .post(`/api/plugin`) + .send({ + source: "Github", + url, + githubToken: "token", + }) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(status ? status : 200) + } + it("should be able to create a plugin from github", async () => { + const res = await createGithubPlugin( + 200, + "https://github.com/my-repo/budibase-comment-box.git" + ) + expect(res.body).toBeDefined() + expect(res.body.plugin).toBeDefined() + expect(res.body.plugin._id).toEqual("plg_comment-box") + }) + it("should fail if the url is not from github", async () => { + const res = await createGithubPlugin( + 400, + "https://notgithub.com/my-repo/budibase-comment-box" + ) + expect(res.body.message).toEqual( + "Failed to import plugin: The plugin origin must be from Github" + ) + }) + }) + describe("npm", () => { + it("should be able to create a plugin from npm", async () => { + const res = await request + .post(`/api/plugin`) + .send({ + source: "NPM", + url: "https://www.npmjs.com/package/budibase-component", + }) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body).toBeDefined() + expect(res.body.plugin._id).toEqual("plg_budibase-component") + expect(events.plugin.imported).toHaveBeenCalled() + }) + }) + + describe("url", () => { + it("should be able to create a plugin from a URL", async () => { + const res = await request + .post(`/api/plugin`) + .send({ + source: "URL", + url: "https://www.someurl.com/comment-box/comment-box-1.0.2.tar.gz", + }) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body).toBeDefined() + expect(res.body.plugin._id).toEqual("plg_comment-box") + expect(events.plugin.imported).toHaveBeenCalledTimes(1) + }) + }) +}) diff --git a/packages/server/src/api/routes/tests/routing.spec.js b/packages/server/src/api/routes/tests/routing.spec.js index 148e5eded1..48762dbf06 100644 --- a/packages/server/src/api/routes/tests/routing.spec.js +++ b/packages/server/src/api/routes/tests/routing.spec.js @@ -9,7 +9,6 @@ const route = "/test" // there are checks which are disabled in test env, // these checks need to be enabled for this test - describe("/routing", () => { let request = setup.getRequest() let config = setup.getConfig() @@ -26,7 +25,7 @@ describe("/routing", () => { screen2.routing.roleId = BUILTIN_ROLE_IDS.POWER screen2.routing.route = route screen2 = await config.createScreen(screen2) - await config.deploy() + await config.publish() }) describe("fetch", () => { diff --git a/packages/server/src/api/routes/tests/row.spec.js b/packages/server/src/api/routes/tests/row.spec.js index 65e6df544a..cd09302dcc 100644 --- a/packages/server/src/api/routes/tests/row.spec.js +++ b/packages/server/src/api/routes/tests/row.spec.js @@ -10,6 +10,7 @@ const { StaticQuotaName, MonthlyQuotaName, } = require("@budibase/types") +const { structures } = require("@budibase/backend-core/tests"); describe("/rows", () => { let request = setup.getRequest() @@ -494,12 +495,13 @@ describe("/rows", () => { describe("attachments", () => { it("should allow enriching attachment rows", async () => { const table = await config.createAttachmentTable() + const attachmentId = `${structures.uuid()}.csv` const row = await config.createRow({ name: "test", description: "test", attachment: [ { - key: `${config.getAppId()}/attachments/test/thing.csv`, + key: `${config.getAppId()}/attachments/${attachmentId}`, }, ], tableId: table._id, @@ -509,7 +511,7 @@ describe("/rows", () => { context.doInAppContext(config.getAppId(), async () => { const enriched = await outputProcessing(table, [row]) expect(enriched[0].attachment[0].url).toBe( - `/prod-budi-app-assets/${config.getAppId()}/attachments/test/thing.csv` + `/files/signed/prod-budi-app-assets/${config.getProdAppId()}/attachments/${attachmentId}` ) }) }) diff --git a/packages/server/src/api/routes/tests/static.spec.js b/packages/server/src/api/routes/tests/static.spec.js index dba8cd76f9..cbfe167100 100644 --- a/packages/server/src/api/routes/tests/static.spec.js +++ b/packages/server/src/api/routes/tests/static.spec.js @@ -1,20 +1,5 @@ -jest.mock("node-fetch") -jest.mock("aws-sdk", () => ({ - config: { - update: jest.fn(), - }, - DynamoDB: { - DocumentClient: jest.fn(), - }, - S3: jest.fn(() => ({ - getSignedUrl: jest.fn(() => { - return "my-url" - }), - })), -})) - const setup = require("./utilities") -const { events, constants } = require("@budibase/backend-core") +const { constants } = require("@budibase/backend-core") describe("/static", () => { let request = setup.getRequest() @@ -102,7 +87,7 @@ describe("/static", () => { .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) - expect(res.body.signedUrl).toEqual("my-url") + expect(res.body.signedUrl).toEqual("http://test.com/foo/bar") expect(res.body.publicUrl).toEqual( `https://${bucket}.s3.eu-west-1.amazonaws.com/${key}` ) diff --git a/packages/server/src/api/routes/tests/table.spec.js b/packages/server/src/api/routes/tests/table.spec.js index 4776878635..b4fd354b9d 100644 --- a/packages/server/src/api/routes/tests/table.spec.js +++ b/packages/server/src/api/routes/tests/table.spec.js @@ -51,7 +51,7 @@ describe("/tables", () => { table.dataImport.schema = table.schema const res = await createTable(table) - + expect(events.table.created).toBeCalledTimes(1) expect(events.table.created).toBeCalledWith(res.body) expect(events.table.imported).toBeCalledTimes(1) @@ -87,6 +87,12 @@ describe("/tables", () => { it("updates all the row fields for a table when a schema key is renamed", async () => { const testTable = await config.createTable() + await config.createView({ + name: "TestView", + field: "Price", + calculation: "stats", + tableId: testTable._id, + }) const testRow = await request .post(`/api/${testTable._id}/rows`) @@ -109,7 +115,7 @@ describe("/tables", () => { updated: "updatedName" }, schema: { - updatedName: {type: "string"} + updatedName: { type: "string" } } }) .set(config.defaultHeaders()) diff --git a/packages/server/src/api/routes/tests/user.spec.js b/packages/server/src/api/routes/tests/user.spec.js index 7e4bf02616..56f1923cb0 100644 --- a/packages/server/src/api/routes/tests/user.spec.js +++ b/packages/server/src/api/routes/tests/user.spec.js @@ -90,4 +90,90 @@ describe("/users", () => { expect(res.body.tableId).toBeDefined() }) }) + describe("setFlag", () => { + it("should throw an error if a flag is not provided", async () => { + await config.createUser() + const res = await request + .post(`/api/users/flags`) + .set(config.defaultHeaders()) + .send({ value: "test" }) + .expect(400) + .expect("Content-Type", /json/) + expect(res.body.message).toEqual("Must supply a 'flag' field in request body.") + + }) + + it("should be able to set a flag on the user", async () => { + await config.createUser() + const res = await request + .post(`/api/users/flags`) + .set(config.defaultHeaders()) + .send({ value: "test", flag: "test" }) + .expect(200) + .expect("Content-Type", /json/) + expect(res.body.message).toEqual("Flag set successfully") + }) + }) + + describe("getFlags", () => { + it("should get flags for a specific user", async () => { + let flagData = { value: "test", flag: "test" } + await config.createUser() + await request + .post(`/api/users/flags`) + .set(config.defaultHeaders()) + .send(flagData) + .expect(200) + .expect("Content-Type", /json/) + + const res = await request + .get(`/api/users/flags`) + .set(config.defaultHeaders()) + .expect(200) + .expect("Content-Type", /json/) + expect(res.body[flagData.value]).toEqual(flagData.flag) + }) + }) + + describe("setFlag", () => { + it("should throw an error if a flag is not provided", async () => { + await config.createUser() + const res = await request + .post(`/api/users/flags`) + .set(config.defaultHeaders()) + .send({ value: "test" }) + .expect(400) + .expect("Content-Type", /json/) + expect(res.body.message).toEqual("Must supply a 'flag' field in request body.") + + }) + + it("should be able to set a flag on the user", async () => { + await config.createUser() + const res = await request + .post(`/api/users/flags`) + .set(config.defaultHeaders()) + .send({ value: "test", flag: "test" }) + .expect(200) + .expect("Content-Type", /json/) + expect(res.body.message).toEqual("Flag set successfully") + }) + }) + + describe("syncUser", () => { + it("should sync the user", async () => { + let user = await config.createUser() + await config.createApp('New App') + let res = await request + .post(`/api/users/metadata/sync/${user._id}`) + .set(config.defaultHeaders()) + .expect(200) + .expect("Content-Type", /json/) + expect(res.body.message).toEqual('User synced.') + }) + }) + + + + }) diff --git a/packages/server/src/api/routes/tests/utilities/index.ts b/packages/server/src/api/routes/tests/utilities/index.ts index 87a373a2c6..519e8a1459 100644 --- a/packages/server/src/api/routes/tests/utilities/index.ts +++ b/packages/server/src/api/routes/tests/utilities/index.ts @@ -63,14 +63,14 @@ export function afterAll() { export function getRequest() { if (!request) { - exports.beforeAll() + beforeAll() } return request } export function getConfig() { if (!config) { - exports.beforeAll() + beforeAll() } return config } diff --git a/packages/server/src/api/routes/tests/webhook.spec.js b/packages/server/src/api/routes/tests/webhook.spec.js index ed2709a0e6..5bdc2ced96 100644 --- a/packages/server/src/api/routes/tests/webhook.spec.js +++ b/packages/server/src/api/routes/tests/webhook.spec.js @@ -113,7 +113,7 @@ describe("/webhooks", () => { describe("trigger", () => { it("should allow triggering from public", async () => { // replicate changes before checking webhook - await config.deploy() + await config.publish() const res = await request .post(`/api/webhooks/trigger/${config.prodAppId}/${webhook._id}`) diff --git a/packages/server/src/automations/automationUtils.ts b/packages/server/src/automations/automationUtils.ts index 8a75de83dd..2d1cb53bc3 100644 --- a/packages/server/src/automations/automationUtils.ts +++ b/packages/server/src/automations/automationUtils.ts @@ -5,6 +5,7 @@ import { } from "@budibase/string-templates" import sdk from "../sdk" import { Row } from "@budibase/types" +import { LoopStep, LoopStepType, LoopInput } from "../definitions/automations" /** * When values are input to the system generally they will be of type string as this is required for template strings. @@ -123,3 +124,26 @@ export function stringSplit(value: string | string[]) { } return value } + +export function typecastForLooping(loopStep: LoopStep, input: LoopInput) { + if (!input || !input.binding) { + return null + } + try { + switch (loopStep.inputs.option) { + case LoopStepType.ARRAY: + if (typeof input.binding === "string") { + return JSON.parse(input.binding) + } + break + case LoopStepType.STRING: + if (Array.isArray(input.binding)) { + return input.binding.join(",") + } + break + } + } catch (err) { + throw new Error("Unable to cast to correct type") + } + return input.binding +} diff --git a/packages/server/src/automations/tests/bash.spec.js b/packages/server/src/automations/tests/bash.spec.js new file mode 100644 index 0000000000..cf358a089d --- /dev/null +++ b/packages/server/src/automations/tests/bash.spec.js @@ -0,0 +1,34 @@ +const setup = require("./utilities") + +describe("test the bash action", () => { + let config = setup.getConfig() + + beforeEach(async () => { + await config.init() + }) + afterAll(setup.afterAll) + + it("should be able to execute a script", async () => { + + let res = await setup.runStep("EXECUTE_BASH", + inputs = { + code: "echo 'test'" + } + + ) + expect(res.stdout).toEqual("test\n") + expect(res.success).toEqual(true) + }) + + it("should handle a null value", async () => { + + let res = await setup.runStep("EXECUTE_BASH", + inputs = { + code: null + } + + + ) + expect(res.stdout).toEqual("Budibase bash automation failed: Invalid inputs") + }) +}) diff --git a/packages/server/src/automations/tests/discord.spec.js b/packages/server/src/automations/tests/discord.spec.js new file mode 100644 index 0000000000..cb51748a04 --- /dev/null +++ b/packages/server/src/automations/tests/discord.spec.js @@ -0,0 +1,27 @@ +const setup = require("./utilities") +const fetch = require("node-fetch") + +jest.mock("node-fetch") + +describe("test the outgoing webhook action", () => { + let inputs + let config = setup.getConfig() + + beforeEach(async () => { + await config.init() + inputs = { + username: "joe_bloggs", + url: "http://www.test.com", + } + }) + + afterAll(setup.afterAll) + + it("should be able to run the action", async () => { + const res = await setup.runStep(setup.actions.discord.stepId, inputs) + expect(res.response.url).toEqual("http://www.test.com") + expect(res.response.method).toEqual("post") + expect(res.success).toEqual(true) + }) + +}) diff --git a/packages/server/src/automations/tests/executeQuery.spec.js b/packages/server/src/automations/tests/executeQuery.spec.js new file mode 100644 index 0000000000..c9b7629d09 --- /dev/null +++ b/packages/server/src/automations/tests/executeQuery.spec.js @@ -0,0 +1,49 @@ +const setup = require("./utilities") + +describe("test the execute query action", () => { + let datasource + let config = setup.getConfig() + + beforeEach(async () => { + await config.init() + + await config.createDatasource() + query = await config.createQuery() + + }) + + afterAll(setup.afterAll) + + it("should be able to execute a query", async () => { + let res = await setup.runStep(setup.actions.EXECUTE_QUERY.stepId, + inputs = { + query: { queryId: query._id } + } + ) + expect(res.response).toEqual([{ a: 'string', b: 1 }]) + expect(res.success).toEqual(true) + }) + + it("should handle a null query value", async () => { + let res = await setup.runStep(setup.actions.EXECUTE_QUERY.stepId, + inputs = { + query: null + } + ) + expect(res.response.message).toEqual("Invalid inputs") + expect(res.success).toEqual(false) + }) + + + it("should handle an error executing a query", async () => { + let res = await setup.runStep(setup.actions.EXECUTE_QUERY.stepId, + inputs = { + query: { queryId: "wrong_id" } + } + ) + expect(res.response).toEqual('{"status":404,"name":"not_found","message":"missing","reason":"missing"}') + expect(res.success).toEqual(false) + }) + + +}) diff --git a/packages/server/src/automations/tests/executeScript.spec.js b/packages/server/src/automations/tests/executeScript.spec.js new file mode 100644 index 0000000000..d0febe4078 --- /dev/null +++ b/packages/server/src/automations/tests/executeScript.spec.js @@ -0,0 +1,48 @@ +const setup = require("./utilities") + +describe("test the execute script action", () => { + let config = setup.getConfig() + + beforeEach(async () => { + await config.init() + }) + afterAll(setup.afterAll) + + it("should be able to execute a script", async () => { + + let res = await setup.runStep(setup.actions.EXECUTE_SCRIPT.stepId, + inputs = { + code: "return 1 + 1" + } + + ) + expect(res.value).toEqual(2) + expect(res.success).toEqual(true) + }) + + it("should handle a null value", async () => { + + let res = await setup.runStep(setup.actions.EXECUTE_SCRIPT.stepId, + inputs = { + code: null + } + + + ) + expect(res.response.message).toEqual("Invalid inputs") + expect(res.success).toEqual(false) + }) + + it("should be able to handle an error gracefully", async () => { + + let res = await setup.runStep(setup.actions.EXECUTE_SCRIPT.stepId, + inputs = { + code: "return something.map(x => x.name)" + } + + ) + expect(res.response).toEqual("ReferenceError: something is not defined") + expect(res.success).toEqual(false) + }) + +}) diff --git a/packages/server/src/automations/tests/sendSmtpEmail.spec.js b/packages/server/src/automations/tests/sendSmtpEmail.spec.js new file mode 100644 index 0000000000..fcafc329c0 --- /dev/null +++ b/packages/server/src/automations/tests/sendSmtpEmail.spec.js @@ -0,0 +1,71 @@ + +function generateResponse(to, from) { + return { + "success": true, + "response": { + "accepted": [ + to + ], + "envelope": { + "from": from, + "to": [ + to + ] + }, + "message": `Email sent to ${to}.` + } + + } +} + +const mockFetch = jest.fn(() => ({ + headers: { + raw: () => { + return { "content-type": ["application/json"] } + }, + get: () => ["application/json"], + }, + json: jest.fn(() => response), + status: 200, + text: jest.fn(), +})) +jest.mock("node-fetch", () => mockFetch) +const setup = require("./utilities") + + +describe("test the outgoing webhook action", () => { + let inputs + let config = setup.getConfig() + beforeEach(async () => { + await config.init() + }) + + afterAll(setup.afterAll) + + it("should be able to run the action", async () => { + inputs = { + to: "user1@test.com", + from: "admin@test.com", + subject: "hello", + contents: "testing", + } + let resp = generateResponse(inputs.to, inputs.from) + mockFetch.mockImplementationOnce(() => ({ + headers: { + raw: () => { + return { "content-type": ["application/json"] } + }, + get: () => ["application/json"], + }, + json: jest.fn(() => resp), + status: 200, + text: jest.fn(), + })) + const res = await setup.runStep(setup.actions.SEND_EMAIL_SMTP.stepId, inputs) + expect(res.response).toEqual(resp) + expect(res.success).toEqual(true) + + }) + + +}) diff --git a/packages/server/src/automations/tests/serverLog.spec.js b/packages/server/src/automations/tests/serverLog.spec.js new file mode 100644 index 0000000000..550910723a --- /dev/null +++ b/packages/server/src/automations/tests/serverLog.spec.js @@ -0,0 +1,22 @@ +const setup = require("./utilities") + +describe("test the server log action", () => { + let config = setup.getConfig() + + beforeEach(async () => { + await config.init() + inputs = { + text: "log message", + } + }) + afterAll(setup.afterAll) + + it("should be able to log the text", async () => { + + let res = await setup.runStep(setup.actions.SERVER_LOG.stepId, + inputs + ) + expect(res.message).toEqual(`App ${config.getAppId()} - ${inputs.text}`) + expect(res.success).toEqual(true) + }) +}) diff --git a/packages/server/src/automations/tests/utilities/index.ts b/packages/server/src/automations/tests/utilities/index.ts index a18e931bab..3c990a38d0 100644 --- a/packages/server/src/automations/tests/utilities/index.ts +++ b/packages/server/src/automations/tests/utilities/index.ts @@ -31,7 +31,7 @@ export async function runInProd(fn: any) { } } -export async function runStep(stepId: string, inputs: any) { +export async function runStep(stepId: string, inputs: any, stepContext?: any) { async function run() { let step = await getAction(stepId) expect(step).toBeDefined() @@ -39,7 +39,7 @@ export async function runStep(stepId: string, inputs: any) { throw new Error("No step found") } return step({ - context: {}, + context: stepContext || {}, inputs, appId: config ? config.getAppId() : null, // don't really need an API key, mocked out usage quota, not being tested here diff --git a/packages/server/src/automations/tests/zapier.spec.js b/packages/server/src/automations/tests/zapier.spec.js new file mode 100644 index 0000000000..e1fc785152 --- /dev/null +++ b/packages/server/src/automations/tests/zapier.spec.js @@ -0,0 +1,27 @@ +const setup = require("./utilities") +const fetch = require("node-fetch") + +jest.mock("node-fetch") + +describe("test the outgoing webhook action", () => { + let inputs + let config = setup.getConfig() + + beforeEach(async () => { + await config.init() + inputs = { + value1: "test", + url: "http://www.test.com", + } + }) + + afterAll(setup.afterAll) + + it("should be able to run the action", async () => { + const res = await setup.runStep(setup.actions.zapier.stepId, inputs) + expect(res.response.url).toEqual("http://www.test.com") + expect(res.response.method).toEqual("post") + expect(res.success).toEqual(true) + }) + +}) diff --git a/packages/server/src/automations/unitTests/automationUtils.spec.js b/packages/server/src/automations/unitTests/automationUtils.spec.js deleted file mode 100644 index 0992bd6eb2..0000000000 --- a/packages/server/src/automations/unitTests/automationUtils.spec.js +++ /dev/null @@ -1,17 +0,0 @@ -const automationUtils = require("../automationUtils") - -describe("automationUtils", () => { - test("substituteLoopStep should allow multiple loop binding substitutes", () => { - expect(automationUtils.substituteLoopStep( - `{{ loop.currentItem._id }} {{ loop.currentItem._id }} {{ loop.currentItem._id }}`, - "step.2")) - .toBe(`{{ step.2.currentItem._id }} {{ step.2.currentItem._id }} {{ step.2.currentItem._id }}`) - }) - - test("substituteLoopStep should handle not subsituting outside of curly braces", () => { - expect(automationUtils.substituteLoopStep( - `loop {{ loop.currentItem._id }}loop loop{{ loop.currentItem._id }}loop`, - "step.2")) - .toBe(`loop {{ step.2.currentItem._id }}loop loop{{ step.2.currentItem._id }}loop`) - }) -}) \ No newline at end of file diff --git a/packages/server/src/automations/unitTests/automationUtils.spec.ts b/packages/server/src/automations/unitTests/automationUtils.spec.ts new file mode 100644 index 0000000000..b80b2d60be --- /dev/null +++ b/packages/server/src/automations/unitTests/automationUtils.spec.ts @@ -0,0 +1,65 @@ +const automationUtils = require("../automationUtils") + +describe("automationUtils", () => { + describe("substituteLoopStep", () => { + it("should allow multiple loop binding substitutes", () => { + expect( + automationUtils.substituteLoopStep( + `{{ loop.currentItem._id }} {{ loop.currentItem._id }} {{ loop.currentItem._id }}`, + "step.2" + ) + ).toBe( + `{{ step.2.currentItem._id }} {{ step.2.currentItem._id }} {{ step.2.currentItem._id }}` + ) + }) + + it("should handle not subsituting outside of curly braces", () => { + expect( + automationUtils.substituteLoopStep( + `loop {{ loop.currentItem._id }}loop loop{{ loop.currentItem._id }}loop`, + "step.2" + ) + ).toBe( + `loop {{ step.2.currentItem._id }}loop loop{{ step.2.currentItem._id }}loop` + ) + }) + }) + + describe("typeCastForLooping", () => { + it("should parse to correct type", () => { + expect( + automationUtils.typecastForLooping( + { inputs: { option: "Array" } }, + { binding: [1, 2, 3] } + ) + ).toEqual([1, 2, 3]) + expect( + automationUtils.typecastForLooping( + { inputs: { option: "Array" } }, + { binding: "[1, 2, 3]" } + ) + ).toEqual([1, 2, 3]) + expect( + automationUtils.typecastForLooping( + { inputs: { option: "String" } }, + { binding: [1, 2, 3] } + ) + ).toEqual("1,2,3") + }) + it("should handle null values", () => { + // expect it to handle where the binding is null + expect( + automationUtils.typecastForLooping( + { inputs: { option: "Array" } }, + { binding: null } + ) + ).toEqual(null) + expect(() => + automationUtils.typecastForLooping( + { inputs: { option: "Array" } }, + { binding: "test" } + ) + ).toThrow() + }) + }) +}) diff --git a/packages/server/src/definitions/openapi.ts b/packages/server/src/definitions/openapi.ts index b4f1ea7c1c..7c02aac060 100644 --- a/packages/server/src/definitions/openapi.ts +++ b/packages/server/src/definitions/openapi.ts @@ -102,6 +102,16 @@ export interface components { lockedBy?: { [key: string]: unknown }; }; }; + deploymentOutput: { + data: { + /** @description The ID of the deployment. */ + _id: string; + /** @description The status of the deployment. */ + status: "SUCCESS" | "FAILURE"; + /** @description The URL by which the published app is accessed. */ + appUrl?: string; + } + }; applicationSearch: { data: { /** @description The name of the app. */ diff --git a/packages/server/src/environment.ts b/packages/server/src/environment.ts index bf5330f9b6..e2b81d88bd 100644 --- a/packages/server/src/environment.ts +++ b/packages/server/src/environment.ts @@ -79,7 +79,6 @@ const environment = { // flags ALLOW_DEV_AUTOMATIONS: process.env.ALLOW_DEV_AUTOMATIONS, DISABLE_THREADING: process.env.DISABLE_THREADING, - DISABLE_DEVELOPER_LICENSE: process.env.DISABLE_DEVELOPER_LICENSE, DISABLE_AUTOMATION_LOGS: process.env.DISABLE_AUTOMATION_LOGS, MULTI_TENANCY: process.env.MULTI_TENANCY, ENABLE_ANALYTICS: process.env.ENABLE_ANALYTICS, @@ -108,17 +107,22 @@ const environment = { } // threading can cause memory issues with node-ts in development -if (isDev() && module.exports.DISABLE_THREADING == null) { +if (isDev() && environment.DISABLE_THREADING == null) { environment._set("DISABLE_THREADING", "1") } // clean up any environment variable edge cases -for (let [key, value] of Object.entries(module.exports)) { +for (let [key, value] of Object.entries(environment)) { // handle the edge case of "0" to disable an environment variable if (value === "0") { // @ts-ignore environment[key] = 0 } + // handle the edge case of "false" to disable an environment variable + if (value === "false") { + // @ts-ignore + environment[key] = 0 + } } export = environment diff --git a/packages/server/src/integrations/base/sql.ts b/packages/server/src/integrations/base/sql.ts index 43ebc8b1dd..a2318eff12 100644 --- a/packages/server/src/integrations/base/sql.ts +++ b/packages/server/src/integrations/base/sql.ts @@ -313,7 +313,8 @@ class InternalBuilder { addRelationships( query: KnexQuery, fromTable: string, - relationships: RelationshipsJson[] | undefined + relationships: RelationshipsJson[] | undefined, + schema: string | undefined ): KnexQuery { if (!relationships) { return query @@ -337,9 +338,13 @@ class InternalBuilder { } for (let [key, relationships] of Object.entries(tableSets)) { const { toTable, throughTable } = JSON.parse(key) + const toTableWithSchema = schema ? `${schema}.${toTable}` : toTable + const throughTableWithSchema = schema + ? `${schema}.${throughTable}` + : throughTable if (!throughTable) { // @ts-ignore - query = query.leftJoin(toTable, function () { + query = query.leftJoin(toTableWithSchema, function () { for (let relationship of relationships) { const from = relationship.from, to = relationship.to @@ -350,7 +355,7 @@ class InternalBuilder { } else { query = query // @ts-ignore - .leftJoin(throughTable, function () { + .leftJoin(throughTableWithSchema, function () { for (let relationship of relationships) { const fromPrimary = relationship.fromPrimary const from = relationship.from @@ -362,7 +367,7 @@ class InternalBuilder { ) } }) - .leftJoin(toTable, function () { + .leftJoin(toTableWithSchema, function () { for (let relationship of relationships) { const toPrimary = relationship.toPrimary const to = relationship.to @@ -456,7 +461,12 @@ class InternalBuilder { preQuery = this.addSorting(preQuery, json) } // handle joins - query = this.addRelationships(preQuery, tableName, relationships) + query = this.addRelationships( + preQuery, + tableName, + relationships, + endpoint.schema + ) return this.addFilters(query, filters, { relationship: true }) } diff --git a/packages/server/src/integrations/googlesheets.ts b/packages/server/src/integrations/googlesheets.ts index 84c2deaa57..d3caf0b944 100644 --- a/packages/server/src/integrations/googlesheets.ts +++ b/packages/server/src/integrations/googlesheets.ts @@ -284,7 +284,7 @@ class GoogleSheetsIntegration implements DatasourcePlus { async createTable(name?: string) { try { await this.connect() - return await this.client.addSheet({ title: name }) + return await this.client.addSheet({ title: name, headerValues: ["test"] }) } catch (err) { console.error("Error creating new table in google sheets", err) throw err diff --git a/packages/server/src/integrations/index.ts b/packages/server/src/integrations/index.ts index e6f5968e32..283deedd70 100644 --- a/packages/server/src/integrations/index.ts +++ b/packages/server/src/integrations/index.ts @@ -98,11 +98,7 @@ export async function getIntegration(integration: string) { for (let plugin of plugins) { if (plugin.name === integration) { // need to use commonJS require due to its dynamic runtime nature - const retrieved: any = await getDatasourcePlugin( - plugin.name, - plugin.jsUrl, - plugin.schema?.hash - ) + const retrieved: any = await getDatasourcePlugin(plugin) if (retrieved.integration) { return retrieved.integration } else { diff --git a/packages/server/src/integrations/mysql.ts b/packages/server/src/integrations/mysql.ts index 136d84361a..1cd9a356b3 100644 --- a/packages/server/src/integrations/mysql.ts +++ b/packages/server/src/integrations/mysql.ts @@ -151,6 +151,9 @@ class MySQLIntegration extends Sql implements DatasourcePlus { ) { return field.string() } + if (field.type === "BIT" && field.length === 1) { + return field.buffer()?.[0] + } return next() }, } diff --git a/packages/server/src/integrations/tests/sql.spec.ts b/packages/server/src/integrations/tests/sql.spec.ts index 421b8535d4..2e45f0754b 100644 --- a/packages/server/src/integrations/tests/sql.spec.ts +++ b/packages/server/src/integrations/tests/sql.spec.ts @@ -51,6 +51,72 @@ function generateDeleteJson(table = TABLE_NAME, filters = {}) { } } +function generateRelationshipJson(config: { schema?: string } = {}) { + return { + endpoint: { + datasourceId: "Postgres", + entityId: "brands", + operation: "READ", + schema: config.schema, + }, + resource: { + fields: [ + "brands.brand_id", + "brands.brand_name", + "products.product_id", + "products.product_name", + "products.brand_id", + ], + }, + filters: {}, + sort: {}, + paginate: {}, + relationships: [ + { + from: "brand_id", + to: "brand_id", + tableName: "products", + column: "products", + }, + ], + extra: { idFilter: {} }, + } +} + +function generateManyRelationshipJson(config: { schema?: string } = {}) { + return { + endpoint: { + datasourceId: "Postgres", + entityId: "stores", + operation: "READ", + schema: config.schema, + }, + resource: { + fields: [ + "stores.store_id", + "stores.store_name", + "products.product_id", + "products.product_name", + ], + }, + filters: {}, + sort: {}, + paginate: {}, + relationships: [ + { + from: "store_id", + to: "product_id", + tableName: "products", + column: "products", + through: "stocks", + fromPrimary: "store_id", + toPrimary: "product_id", + }, + ], + extra: { idFilter: {} }, + } +} + describe("SQL query builder", () => { const limit = 500 const client = SqlClient.POSTGRES @@ -425,4 +491,30 @@ describe("SQL query builder", () => { sql: `select * from (select * from \"${TABLE_NAME}\" where \"${TABLE_NAME}\".\"age\"::jsonb ?| array [20,25] and \"${TABLE_NAME}\".\"name\"::jsonb ?| array ['John','Mary'] limit $1) as \"${TABLE_NAME}\"`, }) }) + + it("should add the schema to the LEFT JOIN", () => { + const query = sql._query(generateRelationshipJson({ schema: "production" })) + expect(query).toEqual({ + bindings: [500, 5000], + sql: `select "brands"."brand_id" as "brands.brand_id", "brands"."brand_name" as "brands.brand_name", "products"."product_id" as "products.product_id", "products"."product_name" as "products.product_name", "products"."brand_id" as "products.brand_id" from (select * from "production"."brands" limit $1) as "brands" left join "production"."products" on "brands"."brand_id" = "products"."brand_id" limit $2`, + }) + }) + + it("should handle if the schema is not present when doing a LEFT JOIN", () => { + const query = sql._query(generateRelationshipJson()) + expect(query).toEqual({ + bindings: [500, 5000], + sql: `select "brands"."brand_id" as "brands.brand_id", "brands"."brand_name" as "brands.brand_name", "products"."product_id" as "products.product_id", "products"."product_name" as "products.product_name", "products"."brand_id" as "products.brand_id" from (select * from "brands" limit $1) as "brands" left join "products" on "brands"."brand_id" = "products"."brand_id" limit $2`, + }) + }) + + it("should add the schema to both the toTable and throughTable in many-to-many join", () => { + const query = sql._query( + generateManyRelationshipJson({ schema: "production" }) + ) + expect(query).toEqual({ + bindings: [500, 5000], + sql: `select "stores"."store_id" as "stores.store_id", "stores"."store_name" as "stores.store_name", "products"."product_id" as "products.product_id", "products"."product_name" as "products.product_name" from (select * from "production"."stores" limit $1) as "stores" left join "production"."stocks" on "stores"."store_id" = "stocks"."store_id" left join "production"."products" on "products"."product_id" = "stocks"."product_id" limit $2`, + }) + }) }) diff --git a/packages/server/src/migrations/functions/backfill/global/configs.ts b/packages/server/src/migrations/functions/backfill/global/configs.ts index 9186a1626a..7eaa987bc7 100644 --- a/packages/server/src/migrations/functions/backfill/global/configs.ts +++ b/packages/server/src/migrations/functions/backfill/global/configs.ts @@ -5,6 +5,7 @@ import { isGoogleConfig, isOIDCConfig, isSettingsConfig, + ConfigType, } from "@budibase/types" import env from "./../../../../environment" @@ -31,15 +32,15 @@ export const backfill = async ( await events.email.SMTPCreated(timestamp) } if (isGoogleConfig(config)) { - await events.auth.SSOCreated("google", timestamp) + await events.auth.SSOCreated(ConfigType.GOOGLE, timestamp) if (config.config.activated) { - await events.auth.SSOActivated("google", timestamp) + await events.auth.SSOActivated(ConfigType.GOOGLE, timestamp) } } if (isOIDCConfig(config)) { - await events.auth.SSOCreated("oidc", timestamp) + await events.auth.SSOCreated(ConfigType.OIDC, timestamp) if (config.config.configs[0].activated) { - await events.auth.SSOActivated("oidc", timestamp) + await events.auth.SSOActivated(ConfigType.OIDC, timestamp) } } if (isSettingsConfig(config)) { diff --git a/packages/server/src/sdk/app/backups/imports.ts b/packages/server/src/sdk/app/backups/imports.ts index 4c2a721c2c..b63d4c5a40 100644 --- a/packages/server/src/sdk/app/backups/imports.ts +++ b/packages/server/src/sdk/app/backups/imports.ts @@ -1,17 +1,16 @@ import { db as dbCore, objectStore } from "@budibase/backend-core" -import { Database } from "@budibase/types" +import { Database, Row } from "@budibase/types" import { getAutomationParams, TABLE_ROW_PREFIX } from "../../../db/utils" import { budibaseTempDir } from "../../../utilities/budibaseDir" import { DB_EXPORT_FILE, GLOBAL_DB_EXPORT_FILE } from "./constants" import { downloadTemplate } from "../../../utilities/fileSystem" -import { FieldTypes, ObjectStoreBuckets } from "../../../constants" +import { ObjectStoreBuckets } from "../../../constants" import { join } from "path" import fs from "fs" import sdk from "../../" import { Automation, AutomationTriggerStepId, - CouchFindOptions, RowAttachment, } from "@budibase/types" const uuid = require("uuid/v4") @@ -25,58 +24,45 @@ type TemplateType = { key?: string } -async function updateAttachmentColumns(prodAppId: string, db: Database) { +function rewriteAttachmentUrl(appId: string, attachment: RowAttachment) { + // URL looks like: /prod-budi-app-assets/appId/attachments/file.csv + const urlParts = attachment.key.split("/") + // remove the app ID + urlParts.shift() + // add new app ID + urlParts.unshift(appId) + const key = urlParts.join("/") + return { + ...attachment, + key, + url: "", // calculated on retrieval using key + } +} + +export async function updateAttachmentColumns(prodAppId: string, db: Database) { // iterate through attachment documents and update them const tables = await sdk.tables.getAllInternalTables(db) + let updatedRows: Row[] = [] for (let table of tables) { - const attachmentCols: string[] = [] - for (let [key, column] of Object.entries(table.schema)) { - if (column.type === FieldTypes.ATTACHMENT) { - attachmentCols.push(key) - } - } - // no attachment columns, nothing to do - if (attachmentCols.length === 0) { - continue - } - // use the CouchDB Mango query API to lookup rows that have attachments - const params: CouchFindOptions = { - selector: { - _id: { - $regex: `^${TABLE_ROW_PREFIX}`, - }, - }, - } - attachmentCols.forEach(col => (params.selector[col] = { $exists: true })) - const { rows } = await dbCore.directCouchFind(db.name, params) - for (let row of rows) { - for (let column of attachmentCols) { - if (!Array.isArray(row[column])) { - continue - } - row[column] = row[column].map((attachment: RowAttachment) => { - // URL looks like: /prod-budi-app-assets/appId/attachments/file.csv - const urlParts = attachment.url.split("/") - // drop the first empty element - urlParts.shift() - // get the prefix - const prefix = urlParts.shift() - // remove the app ID - urlParts.shift() - // add new app ID - urlParts.unshift(prodAppId) - const key = urlParts.join("/") - return { - ...attachment, - key, - url: `/${prefix}/${key}`, + const { rows, columns } = await sdk.rows.getRowsWithAttachments( + db.name, + table + ) + updatedRows = updatedRows.concat( + rows.map(row => { + for (let column of columns) { + if (Array.isArray(row[column])) { + row[column] = row[column].map((attachment: RowAttachment) => + rewriteAttachmentUrl(prodAppId, attachment) + ) } - }) - } - } - // write back the updated attachments - await db.bulkDocs(rows) + } + return row + }) + ) } + // write back the updated attachments + await db.bulkDocs(updatedRows) } async function updateAutomations(prodAppId: string, db: Database) { diff --git a/packages/server/src/sdk/app/rows/attachments.ts b/packages/server/src/sdk/app/rows/attachments.ts new file mode 100644 index 0000000000..67f58f8f2c --- /dev/null +++ b/packages/server/src/sdk/app/rows/attachments.ts @@ -0,0 +1,60 @@ +import { CouchFindOptions, Table, Row } from "@budibase/types" +import { db as dbCore } from "@budibase/backend-core" +import { DocumentType, SEPARATOR } from "../../../db/utils" +import { FieldTypes } from "../../../constants" + +// default limit - seems to work well for performance +export const FIND_LIMIT = 25 + +function generateAttachmentFindParams( + tableId: string, + attachmentCols: string[], + bookmark: null | string +) { + const params: CouchFindOptions = { + selector: { + _id: { + $regex: `^${DocumentType.ROW}${SEPARATOR}${tableId}`, + }, + }, + limit: FIND_LIMIT, + } + attachmentCols.forEach(col => (params.selector[col] = { $exists: true })) + if (bookmark) { + params.bookmark = bookmark + } + return params +} + +export async function getRowsWithAttachments(appId: string, table: Table) { + // iterate through attachment documents and update them + const db = dbCore.getDB(appId) + const attachmentCols: string[] = [] + for (let [key, column] of Object.entries(table.schema)) { + if (column.type === FieldTypes.ATTACHMENT) { + attachmentCols.push(key) + } + } + // no attachment columns, nothing to do + if (attachmentCols.length === 0) { + return { rows: [], columns: [] } + } + let bookmark: null | string = null, + rowsLength = 0, + rowList: Row[] = [] + do { + const params = generateAttachmentFindParams( + table._id!, + attachmentCols, + bookmark + ) + // use the CouchDB Mango query API to lookup rows that have attachments + const resp = await dbCore.directCouchFind(db.name, params) + bookmark = resp.bookmark + rowsLength = resp.rows.length + const rows = resp.rows + rowList = rowList.concat(rows) + } while (rowsLength === FIND_LIMIT) + // write back the updated attachments + return { rows: rowList, columns: attachmentCols } +} diff --git a/packages/server/src/sdk/app/rows/index.ts b/packages/server/src/sdk/app/rows/index.ts new file mode 100644 index 0000000000..12a44ded67 --- /dev/null +++ b/packages/server/src/sdk/app/rows/index.ts @@ -0,0 +1,7 @@ +import * as attachments from "./attachments" +import * as rows from "./rows" + +export default { + ...attachments, + ...rows, +} diff --git a/packages/server/src/sdk/app/rows/rows.ts b/packages/server/src/sdk/app/rows/rows.ts new file mode 100644 index 0000000000..8709180f0b --- /dev/null +++ b/packages/server/src/sdk/app/rows/rows.ts @@ -0,0 +1,18 @@ +import { db as dbCore, context } from "@budibase/backend-core" +import { Database, Row } from "@budibase/types" +import { getRowParams } from "../../../db/utils" + +export async function getAllInternalRows(appId?: string) { + let db: Database + if (appId) { + db = dbCore.getDB(appId) + } else { + db = context.getAppDB() + } + const response = await db.allDocs( + getRowParams(null, null, { + include_docs: true, + }) + ) + return response.rows.map(row => row.doc) as Row[] +} diff --git a/packages/server/src/sdk/index.ts b/packages/server/src/sdk/index.ts index 19df8b4388..2ee40992c8 100644 --- a/packages/server/src/sdk/index.ts +++ b/packages/server/src/sdk/index.ts @@ -2,6 +2,7 @@ import { default as backups } from "./app/backups" import { default as tables } from "./app/tables" import { default as automations } from "./app/automations" import { default as applications } from "./app/applications" +import { default as rows } from "./app/rows" import { default as users } from "./users" const sdk = { @@ -9,6 +10,7 @@ const sdk = { tables, automations, applications, + rows, users, } diff --git a/packages/server/src/sdk/tests/attachments.spec.ts b/packages/server/src/sdk/tests/attachments.spec.ts new file mode 100644 index 0000000000..f1abb246f8 --- /dev/null +++ b/packages/server/src/sdk/tests/attachments.spec.ts @@ -0,0 +1,76 @@ +import newid from "../../db/newid" + +const attachment = { + size: 73479, + name: "2022-12-14 11_11_44-.png", + url: "/prod-budi-app-assets/app_bbb/attachments/a.png", + extension: "png", + key: "app_bbb/attachments/a.png", +} + +const row = { + _id: "ro_ta_aaa", + photo: [attachment], + otherCol: "string", +} + +const table = { + _id: "ta_aaa", + name: "photos", + schema: { + photo: { + type: "attachment", + name: "photo", + }, + otherCol: { + type: "string", + name: "otherCol", + }, + }, +} + +jest.mock("@budibase/backend-core", () => { + const core = jest.requireActual("@budibase/backend-core") + return { + ...core, + db: { + ...core.db, + directCouchFind: jest.fn(), + }, + } +}) + +import { db as dbCore } from "@budibase/backend-core" +import sdk from "../index" + +describe("should be able to re-write attachment URLs", () => { + it("it should update URLs on a number of rows over the limit", async () => { + const db = dbCore.getDB("app_aaa") + await db.put(table) + const limit = 30 + let rows = [] + for (let i = 0; i < limit; i++) { + const rowToWrite = { + ...row, + _id: `${row._id}_${newid()}`, + } + const { rev } = await db.put(rowToWrite) + rows.push({ + ...rowToWrite, + _rev: rev, + }) + } + + dbCore.directCouchFind + // @ts-ignore + .mockReturnValueOnce({ rows: rows.slice(0, 25), bookmark: "aaa" }) + .mockReturnValueOnce({ rows: rows.slice(25, limit), bookmark: "bbb" }) + await sdk.backups.updateAttachmentColumns(db.name, db) + const finalRows = await sdk.rows.getAllInternalRows(db.name) + for (let rowToCheck of finalRows) { + expect(rowToCheck.otherCol).toBe(row.otherCol) + expect(rowToCheck.photo[0].url).toBe("") + expect(rowToCheck.photo[0].key).toBe(`${db.name}/attachments/a.png`) + } + }) +}) diff --git a/packages/server/src/tests/jestSetup.ts b/packages/server/src/tests/jestSetup.ts index c178cb4449..b53ac2d9fa 100644 --- a/packages/server/src/tests/jestSetup.ts +++ b/packages/server/src/tests/jestSetup.ts @@ -19,7 +19,10 @@ import { mocks } from "@budibase/backend-core/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 + global.console.warn = jest.fn() // console.warn are ignored in tests +} if (!process.env.CI) { // set a longer timeout in dev for debugging diff --git a/packages/server/src/tests/utilities/TestConfiguration.ts b/packages/server/src/tests/utilities/TestConfiguration.ts index bbd940150f..3ef9c6b513 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.ts +++ b/packages/server/src/tests/utilities/TestConfiguration.ts @@ -1,3 +1,12 @@ +import { mocks } from "@budibase/backend-core/tests" + +// init the licensing mock +import * as pro from "@budibase/pro" +mocks.licenses.init(pro) + +// use unlimited license by default +mocks.licenses.useUnlimited() + import { init as dbInit } from "../../db" dbInit() import env from "../../environment" @@ -351,7 +360,6 @@ class TestConfiguration { } // APP - async createApp(appName: string) { // create dev app // clear any old app @@ -364,7 +372,7 @@ class TestConfiguration { await context.updateAppId(this.appId) // create production app - this.prodApp = await this.deploy() + this.prodApp = await this.publish() this.allApps.push(this.prodApp) this.allApps.push(this.app) @@ -372,8 +380,8 @@ class TestConfiguration { return this.app } - async deploy() { - await this._req(null, null, controllers.deploy.deployApp) + async publish() { + await this._req(null, null, controllers.deploy.publishApp) // @ts-ignore const prodAppId = this.getAppId().replace("_dev", "") this.prodAppId = prodAppId @@ -384,6 +392,17 @@ class TestConfiguration { }) } + async unpublish() { + const response = await this._req( + null, + { appId: this.appId }, + controllers.app.unpublish + ) + this.prodAppId = null + this.prodApp = null + return response + } + // TABLE async updateTable(config?: any) { diff --git a/packages/server/src/threads/automation.ts b/packages/server/src/threads/automation.ts index 8b343cdf8e..44951869f4 100644 --- a/packages/server/src/threads/automation.ts +++ b/packages/server/src/threads/automation.ts @@ -32,31 +32,8 @@ const LOOP_STEP_ID = actions.ACTION_DEFINITIONS.LOOP.stepId const CRON_STEP_ID = triggerDefs.CRON.stepId const STOPPED_STATUS = { success: true, status: AutomationStatus.STOPPED } -function typecastForLooping(loopStep: LoopStep, input: LoopInput) { - if (!input || !input.binding) { - return null - } - try { - switch (loopStep.inputs.option) { - case LoopStepType.ARRAY: - if (typeof input.binding === "string") { - return JSON.parse(input.binding) - } - break - case LoopStepType.STRING: - if (Array.isArray(input.binding)) { - return input.binding.join(",") - } - break - } - } catch (err) { - throw new Error("Unable to cast to correct type") - } - return input.binding -} - function getLoopIterations(loopStep: LoopStep, input: LoopInput) { - const binding = typecastForLooping(loopStep, input) + const binding = automationUtils.typecastForLooping(loopStep, input) if (!loopStep || !binding) { return 1 } @@ -289,7 +266,7 @@ class Orchestrator { let tempOutput = { items: loopSteps, iterations: iterationCount } try { - newInput.binding = typecastForLooping( + newInput.binding = automationUtils.typecastForLooping( loopStep as LoopStep, newInput ) @@ -442,7 +419,7 @@ class Orchestrator { // Delete the step after the loop step as it's irrelevant, since information is included // in the loop step - if (wasLoopStep) { + if (wasLoopStep && !loopStep) { this._context.steps.splice(loopStepNumber + 1, 1) wasLoopStep = false } @@ -461,8 +438,8 @@ class Orchestrator { }) this._context.steps[loopStepNumber] = tempOutput - loopSteps = undefined wasLoopStep = true + loopSteps = [] } } diff --git a/packages/server/src/utilities/fileSystem/app.ts b/packages/server/src/utilities/fileSystem/app.ts new file mode 100644 index 0000000000..b8976336ac --- /dev/null +++ b/packages/server/src/utilities/fileSystem/app.ts @@ -0,0 +1,86 @@ +import { budibaseTempDir } from "../budibaseDir" +import fs from "fs" +import { join } from "path" +import { ObjectStoreBuckets } from "../../constants" +import { updateClientLibrary } from "./clientLibrary" +import env from "../../environment" +import { objectStore, context } from "@budibase/backend-core" +import { TOP_LEVEL_PATH } from "./filesystem" + +export const NODE_MODULES_PATH = join(TOP_LEVEL_PATH, "node_modules") + +/** + * Uploads the latest client library to the object store. + * @param {string} appId The ID of the app which is being created. + * @return {Promise} once promise completes app resources should be ready in object store. + */ +export const createApp = async (appId: string) => { + await updateClientLibrary(appId) +} + +/** + * Removes all of the assets created for an app in the object store. + * @param {string} appId The ID of the app which is being deleted. + * @return {Promise} once promise completes the app resources will be removed from object store. + */ +export const deleteApp = async (appId: string) => { + await objectStore.deleteFolder(ObjectStoreBuckets.APPS, `${appId}/`) +} + +/** + * Retrieves component libraries from object store (or tmp symlink if in local) + */ +export const getComponentLibraryManifest = async (library: string) => { + const appId = context.getAppId() + const filename = "manifest.json" + /* istanbul ignore next */ + // when testing in cypress and so on we need to get the package + // as the environment may not be fully fleshed out for dev or prod + if (env.isTest()) { + library = library.replace("standard-components", "client") + const lib = library.split("/")[1] + const path = require.resolve(library).split(lib)[0] + return require(join(path, lib, filename)) + } else if (env.isDev()) { + const path = join(NODE_MODULES_PATH, "@budibase", "client", filename) + // always load from new so that updates are refreshed + delete require.cache[require.resolve(path)] + return require(path) + } + + if (!appId) { + throw new Error("No app ID found - cannot get component libraries") + } + + let resp + let path + try { + // Try to load the manifest from the new file location + path = join(appId, filename) + resp = await objectStore.retrieve(ObjectStoreBuckets.APPS, path) + } catch (error) { + console.error( + `component-manifest-objectstore=failed appId=${appId} path=${path}`, + error + ) + // Fallback to loading it from the old location for old apps + path = join(appId, "node_modules", library, "package", filename) + resp = await objectStore.retrieve(ObjectStoreBuckets.APPS, path) + } + if (typeof resp !== "string") { + resp = resp.toString("utf8") + } + return JSON.parse(resp) +} + +/** + * Given a set of app IDs makes sure file system is cleared of any of their temp info. + */ +export const cleanup = (appIds: string[]) => { + for (let appId of appIds) { + const path = join(budibaseTempDir(), appId) + if (fs.existsSync(path)) { + fs.rmdirSync(path, { recursive: true }) + } + } +} diff --git a/packages/server/src/utilities/fileSystem/clientLibrary.ts b/packages/server/src/utilities/fileSystem/clientLibrary.ts index 9fb96ff9a5..da4decb3e3 100644 --- a/packages/server/src/utilities/fileSystem/clientLibrary.ts +++ b/packages/server/src/utilities/fileSystem/clientLibrary.ts @@ -4,7 +4,7 @@ import fs from "fs" import { objectStore } from "@budibase/backend-core" import { resolve } from "../centralPath" import env from "../../environment" -const TOP_LEVEL_PATH = join(__dirname, "..", "..", "..") +import { TOP_LEVEL_PATH } from "./filesystem" /** * Client library paths in the object store: diff --git a/packages/server/src/utilities/fileSystem/filesystem.ts b/packages/server/src/utilities/fileSystem/filesystem.ts new file mode 100644 index 0000000000..0c9f1ee8cd --- /dev/null +++ b/packages/server/src/utilities/fileSystem/filesystem.ts @@ -0,0 +1,170 @@ +import { PathLike } from "fs" +const { budibaseTempDir } = require("../budibaseDir") +const fs = require("fs") +const { join } = require("path") +const uuid = require("uuid/v4") +const env = require("../../environment") +import tar from "tar" + +export const TOP_LEVEL_PATH = join(__dirname, "..", "..", "..") + +/** + * Upon first startup of instance there may not be everything we need in tmp directory, set it up. + */ +export const init = () => { + const tempDir = budibaseTempDir() + if (!fs.existsSync(tempDir)) { + // some test cases fire this quickly enough that + // synchronous cases can end up here at the same time + try { + fs.mkdirSync(tempDir) + } catch (err: any) { + if (!err || err.code !== "EEXIST") { + throw err + } + } + } + const clientLibPath = join(budibaseTempDir(), "budibase-client.js") + if (env.isTest() && !fs.existsSync(clientLibPath)) { + fs.copyFileSync(require.resolve("@budibase/client"), clientLibPath) + } +} + +/** + * Checks if the system is currently in development mode and if it is makes sure + * everything required to function is ready. + */ +export const checkDevelopmentEnvironment = () => { + if (!env.isDev() || env.isTest()) { + return + } + if (!fs.existsSync(budibaseTempDir())) { + fs.mkdirSync(budibaseTempDir()) + } + let error + if (!fs.existsSync(join(process.cwd(), ".env"))) { + error = "Must run via yarn once to generate environment." + } + if (error) { + console.error(error) + process.exit(-1) + } +} + +/** + * Used to retrieve a handlebars file from the system which will be used as a template. + * This is allowable as the template handlebars files should be static and identical across + * the cluster. + * @param {string} path The path to the handlebars file which is to be loaded. + * @returns {string} The loaded handlebars file as a string - loaded as utf8. + */ +export const loadHandlebarsFile = (path: PathLike) => { + return fs.readFileSync(path, "utf8") +} + +/** + * When return a file from the API need to write the file to the system temporarily so we + * can create a read stream to send. + * @param {string} contents the contents of the file which is to be returned from the API. + * @return {Object} the read stream which can be put into the koa context body. + */ +export const apiFileReturn = (contents: any) => { + const path = join(budibaseTempDir(), uuid()) + fs.writeFileSync(path, contents) + return fs.createReadStream(path) +} + +export const streamFile = (path: string) => { + return fs.createReadStream(path) +} + +/** + * Writes the provided contents to a temporary file, which can be used briefly. + * @param {string} fileContents contents which will be written to a temp file. + * @return {string} the path to the temp file. + */ +export const storeTempFile = (fileContents: any) => { + const path = join(budibaseTempDir(), uuid()) + fs.writeFileSync(path, fileContents) + return path +} + +/** + * Utility function for getting a file read stream - a simple in memory buffered read + * stream doesn't work for pouchdb. + */ +export const stringToFileStream = (contents: any) => { + const path = storeTempFile(contents) + return fs.createReadStream(path) +} + +/** + * Creates a temp file and returns it from the API. + * @param {string} fileContents the contents to be returned in file. + */ +export const sendTempFile = (fileContents: any) => { + const path = storeTempFile(fileContents) + return fs.createReadStream(path) +} + +/** + * All file reads come through here just to make sure all of them make sense + * allows a centralised location to check logic is all good. + */ +export const readFileSync = (filepath: PathLike, options = "utf8") => { + return fs.readFileSync(filepath, options) +} + +export const createTempFolder = (item: any) => { + const path = join(budibaseTempDir(), item) + try { + // remove old tmp directories automatically - don't combine + if (fs.existsSync(path)) { + fs.rmSync(path, { recursive: true, force: true }) + } + fs.mkdirSync(path) + } catch (err: any) { + throw new Error(`Path cannot be created: ${err.message}`) + } + + return path +} + +export const extractTarball = async (fromFilePath: string, toPath: string) => { + await tar.extract({ + file: fromFilePath, + C: toPath, + }) +} + +/** + * Find for a file recursively from start path applying filter, return first match + */ +export const findFileRec = (startPath: PathLike, filter: string): any => { + if (!fs.existsSync(startPath)) { + return + } + + const files = fs.readdirSync(startPath) + for (let i = 0, len = files.length; i < len; i++) { + const filename = join(startPath, files[i]) + const stat = fs.lstatSync(filename) + + if (stat.isDirectory()) { + return findFileRec(filename, filter) + } else if (filename.endsWith(filter)) { + return filename + } + } +} + +/** + * Remove a folder which is not empty from the file system + */ +export const deleteFolderFileSystem = (path: PathLike) => { + if (!fs.existsSync(path)) { + return + } + + fs.rmSync(path, { recursive: true, force: true }) +} diff --git a/packages/server/src/utilities/fileSystem/index.ts b/packages/server/src/utilities/fileSystem/index.ts index 58a687c31b..836ee3aebd 100644 --- a/packages/server/src/utilities/fileSystem/index.ts +++ b/packages/server/src/utilities/fileSystem/index.ts @@ -1,348 +1,5 @@ -import { budibaseTempDir } from "../budibaseDir" -import fs from "fs" -import { join } from "path" -import { context, objectStore } from "@budibase/backend-core" -import { ObjectStoreBuckets } from "../../constants" -import { updateClientLibrary } from "./clientLibrary" -import { checkSlashesInUrl } from "../" -import env from "../../environment" -import fetch from "node-fetch" -const uuid = require("uuid/v4") -const tar = require("tar") - -export const TOP_LEVEL_PATH = join(__dirname, "..", "..", "..") -export const NODE_MODULES_PATH = join(TOP_LEVEL_PATH, "node_modules") -const DATASOURCE_PATH = join(budibaseTempDir(), "datasource") - -/** - * The single stack system (Cloud and Builder) should not make use of the file system where possible, - * this file handles all of the file access for the system with the intention of limiting it all to one - * place. Keeping all of this logic in one place means that when we need to do file system access (like - * downloading a package or opening a temporary file) in can be done in way that we can confirm it shouldn't - * be done through an object store instead. - */ - -/** - * Upon first startup of instance there may not be everything we need in tmp directory, set it up. - */ -export function init() { - const tempDir = budibaseTempDir() - if (!fs.existsSync(tempDir)) { - // some test cases fire this quickly enough that - // synchronous cases can end up here at the same time - try { - fs.mkdirSync(tempDir) - } catch (err: any) { - if (!err || err.code !== "EEXIST") { - throw err - } - } - } - const clientLibPath = join(budibaseTempDir(), "budibase-client.js") - if (env.isTest() && !fs.existsSync(clientLibPath)) { - fs.copyFileSync(require.resolve("@budibase/client"), clientLibPath) - } -} - -/** - * Checks if the system is currently in development mode and if it is makes sure - * everything required to function is ready. - */ -exports.checkDevelopmentEnvironment = () => { - if (!env.isDev() || env.isTest()) { - return - } - if (!fs.existsSync(budibaseTempDir())) { - fs.mkdirSync(budibaseTempDir()) - } - let error - if (!fs.existsSync(join(process.cwd(), ".env"))) { - error = "Must run via yarn once to generate environment." - } - if (error) { - console.error(error) - process.exit(-1) - } -} - -/** - * Used to retrieve a handlebars file from the system which will be used as a template. - * This is allowable as the template handlebars files should be static and identical across - * the cluster. - * @param {string} path The path to the handlebars file which is to be loaded. - * @returns {string} The loaded handlebars file as a string - loaded as utf8. - */ -export function loadHandlebarsFile(path: string) { - return fs.readFileSync(path, "utf8") -} - -/** - * When return a file from the API need to write the file to the system temporarily so we - * can create a read stream to send. - * @param {string} contents the contents of the file which is to be returned from the API. - * @param {string} encoding the encoding of the file to return (utf8 default) - * @return {Object} the read stream which can be put into the koa context body. - */ -export function apiFileReturn( - contents: string, - encoding: BufferEncoding = "utf8" -) { - const path = join(budibaseTempDir(), uuid()) - fs.writeFileSync(path, contents, { encoding }) - return fs.createReadStream(path, { encoding }) -} - -export function streamFile(path: string) { - return fs.createReadStream(path) -} - -/** - * Writes the provided contents to a temporary file, which can be used briefly. - * @param {string} fileContents contents which will be written to a temp file. - * @return {string} the path to the temp file. - */ -export function storeTempFile(fileContents: string) { - const path = join(budibaseTempDir(), uuid()) - fs.writeFileSync(path, fileContents) - return path -} - -/** - * Utility function for getting a file read stream - a simple in memory buffered read - * stream doesn't work for pouchdb. - */ -export function stringToFileStream(contents: string) { - const path = exports.storeTempFile(contents) - return fs.createReadStream(path) -} - -/** - * Creates a temp file and returns it from the API. - * @param {string} fileContents the contents to be returned in file. - */ -export function sendTempFile(fileContents: string) { - const path = exports.storeTempFile(fileContents) - return fs.createReadStream(path) -} - -/** - * Uploads the latest client library to the object store. - * @param {string} appId The ID of the app which is being created. - * @return {Promise} once promise completes app resources should be ready in object store. - */ -export async function createApp(appId: string) { - await updateClientLibrary(appId) -} - -/** - * Removes all of the assets created for an app in the object store. - * @param {string} appId The ID of the app which is being deleted. - * @return {Promise} once promise completes the app resources will be removed from object store. - */ -export async function deleteApp(appId: string) { - await objectStore.deleteFolder(ObjectStoreBuckets.APPS, `${appId}/`) -} - -/** - * Retrieves a template and pipes it to minio as well as making it available temporarily. - * @param {string} type The type of template which is to be retrieved. - * @param name - * @return {Promise<*>} - */ -export async function downloadTemplate(type: string, name: string) { - const DEFAULT_TEMPLATES_BUCKET = - "prod-budi-templates.s3-eu-west-1.amazonaws.com" - const templateUrl = `https://${DEFAULT_TEMPLATES_BUCKET}/templates/${type}/${name}.tar.gz` - return objectStore.downloadTarball( - templateUrl, - ObjectStoreBuckets.TEMPLATES, - type - ) -} - -/** - * Retrieves component libraries from object store (or tmp symlink if in local) - */ -export async function getComponentLibraryManifest(library: string) { - const appId = context.getAppId() - const filename = "manifest.json" - /* istanbul ignore next */ - // when testing in cypress and so on we need to get the package - // as the environment may not be fully fleshed out for dev or prod - if (env.isTest()) { - library = library.replace("standard-components", "client") - const lib = library.split("/")[1] - const path = require.resolve(library).split(lib)[0] - return require(join(path, lib, filename)) - } else if (env.isDev()) { - const path = join(NODE_MODULES_PATH, "@budibase", "client", filename) - // always load from new so that updates are refreshed - delete require.cache[require.resolve(path)] - return require(path) - } - - if (!appId) { - throw new Error("No app ID found - cannot get component libraries") - } - - let resp - let path - try { - // Try to load the manifest from the new file location - path = join(appId, filename) - resp = await objectStore.retrieve(ObjectStoreBuckets.APPS, path) - } catch (error) { - console.error( - `component-manifest-objectstore=failed appId=${appId} path=${path}`, - error - ) - // Fallback to loading it from the old location for old apps - path = join(appId, "node_modules", library, "package", filename) - resp = await objectStore.retrieve(ObjectStoreBuckets.APPS, path) - } - if (typeof resp !== "string") { - resp = resp.toString("utf8") - } - return JSON.parse(resp) -} - -/** - * All file reads come through here just to make sure all of them make sense - * allows a centralised location to check logic is all good. - */ -export function readFileSync( - filepath: string, - options: BufferEncoding = "utf8" -) { - return fs.readFileSync(filepath, { encoding: options }) -} - -/** - * Given a set of app IDs makes sure file system is cleared of any of their temp info. - */ -export function cleanup(appIds: string[]) { - for (let appId of appIds) { - const path = join(budibaseTempDir(), appId) - if (fs.existsSync(path)) { - fs.rmdirSync(path, { recursive: true }) - } - } -} - -export function createTempFolder(item: string) { - const path = join(budibaseTempDir(), item) - try { - // remove old tmp directories automatically - don't combine - if (fs.existsSync(path)) { - fs.rmSync(path, { recursive: true, force: true }) - } - fs.mkdirSync(path) - } catch (err: any) { - throw new Error(`Path cannot be created: ${err.message}`) - } - - return path -} - -export async function extractTarball(fromFilePath: string, toPath: string) { - await tar.extract({ - file: fromFilePath, - C: toPath, - }) -} - -export async function getPluginMetadata(path: string) { - let metadata: { schema?: any; package?: any } = {} - try { - const pkg = fs.readFileSync(join(path, "package.json"), "utf8") - const schema = fs.readFileSync(join(path, "schema.json"), "utf8") - - metadata.schema = JSON.parse(schema) - metadata.package = JSON.parse(pkg) - - if ( - !metadata.package.name || - !metadata.package.version || - !metadata.package.description - ) { - throw new Error( - "package.json is missing one of 'name', 'version' or 'description'." - ) - } - } catch (err: any) { - throw new Error( - `Unable to process schema.json/package.json in plugin. ${err.message}` - ) - } - - return { metadata, directory: path } -} - -export async function getDatasourcePlugin( - name: string, - url: string, - hash: string -) { - if (!fs.existsSync(DATASOURCE_PATH)) { - fs.mkdirSync(DATASOURCE_PATH) - } - const filename = join(DATASOURCE_PATH, name) - const metadataName = `${filename}.bbmetadata` - if (fs.existsSync(filename)) { - const currentHash = fs.readFileSync(metadataName, "utf8") - // if hash is the same return the file, otherwise remove it and re-download - if (currentHash === hash) { - return require(filename) - } else { - console.log(`Updating plugin: ${name}`) - delete require.cache[require.resolve(filename)] - fs.unlinkSync(filename) - } - } - const fullUrl = checkSlashesInUrl( - `${env.MINIO_URL}/${ObjectStoreBuckets.PLUGINS}/${url}` - ) - const response = await fetch(fullUrl) - if (response.status === 200) { - const content = await response.text() - fs.writeFileSync(filename, content) - fs.writeFileSync(metadataName, hash) - return require(filename) - } else { - throw new Error( - `Unable to retrieve plugin - reason: ${await response.text()}` - ) - } -} - -/** - * Find for a file recursively from start path applying filter, return first match - */ -export function findFileRec(startPath: string, filter: any) { - if (!fs.existsSync(startPath)) { - return - } - - const files = fs.readdirSync(startPath) - for (let i = 0, len = files.length; i < len; i++) { - const filename = join(startPath, files[i]) - const stat = fs.lstatSync(filename) - - if (stat.isDirectory()) { - return exports.findFileRec(filename, filter) - } else if (filename.endsWith(filter)) { - return filename - } - } -} - -/** - * Remove a folder which is not empty from the file system - */ -export function deleteFolderFileSystem(path: string) { - if (!fs.existsSync(path)) { - return - } - - fs.rmSync(path, { recursive: true, force: true }) -} +export * from "./app" +export * from "./clientLibrary" +export * from "./filesystem" +export * from "./plugin" +export * from "./template" diff --git a/packages/server/src/utilities/fileSystem/plugin.ts b/packages/server/src/utilities/fileSystem/plugin.ts new file mode 100644 index 0000000000..ea1742faba --- /dev/null +++ b/packages/server/src/utilities/fileSystem/plugin.ts @@ -0,0 +1,65 @@ +import { Plugin } from "@budibase/types" + +const { budibaseTempDir } = require("../budibaseDir") +const fs = require("fs") +const { join } = require("path") +import { objectStore } from "@budibase/backend-core" + +const DATASOURCE_PATH = join(budibaseTempDir(), "datasource") + +export const getPluginMetadata = async (path: string) => { + let metadata: any = {} + try { + const pkg = fs.readFileSync(join(path, "package.json"), "utf8") + const schema = fs.readFileSync(join(path, "schema.json"), "utf8") + + metadata.schema = JSON.parse(schema) + metadata.package = JSON.parse(pkg) + + if ( + !metadata.package.name || + !metadata.package.version || + !metadata.package.description + ) { + throw new Error( + "package.json is missing one of 'name', 'version' or 'description'." + ) + } + } catch (err: any) { + throw new Error( + `Unable to process schema.json/package.json in plugin. ${err.message}` + ) + } + + return { metadata, directory: path } +} + +export const getDatasourcePlugin = async (plugin: Plugin) => { + const hash = plugin.schema?.hash + if (!fs.existsSync(DATASOURCE_PATH)) { + fs.mkdirSync(DATASOURCE_PATH) + } + const filename = join(DATASOURCE_PATH, plugin.name) + const metadataName = `${filename}.bbmetadata` + if (fs.existsSync(filename)) { + const currentHash = fs.readFileSync(metadataName, "utf8") + // if hash is the same return the file, otherwise remove it and re-download + if (currentHash === hash) { + return require(filename) + } else { + console.log(`Updating plugin: ${plugin.name}`) + delete require.cache[require.resolve(filename)] + fs.unlinkSync(filename) + } + } + const pluginKey = objectStore.getPluginJSKey(plugin) + const pluginJs = await objectStore.retrieve( + objectStore.ObjectStoreBuckets.PLUGINS, + pluginKey + ) + + fs.writeFileSync(filename, pluginJs) + fs.writeFileSync(metadataName, hash) + + return require(filename) +} diff --git a/packages/server/src/utilities/fileSystem/template.ts b/packages/server/src/utilities/fileSystem/template.ts new file mode 100644 index 0000000000..17a959007e --- /dev/null +++ b/packages/server/src/utilities/fileSystem/template.ts @@ -0,0 +1,36 @@ +const fs = require("fs") +const { join } = require("path") +import { ObjectStoreBuckets } from "../../constants" +import { objectStore } from "@budibase/backend-core" + +/** + * This function manages temporary template files which are stored by Koa. + * @param {Object} template The template object retrieved from the Koa context object. + * @returns {Object} Returns an fs read stream which can be loaded into the database. + */ +export const getTemplateStream = async (template: any) => { + if (template.file) { + return fs.createReadStream(template.file.path) + } else { + const [type, name] = template.key.split("/") + const tmpPath = await downloadTemplate(type, name) + return fs.createReadStream(join(tmpPath, name, "db", "dump.txt")) + } +} + +/** + * Retrieves a template and pipes it to minio as well as making it available temporarily. + * @param {string} type The type of template which is to be retrieved. + * @param name + * @return {Promise<*>} + */ +export const downloadTemplate = async (type: string, name: string) => { + const DEFAULT_TEMPLATES_BUCKET = + "prod-budi-templates.s3-eu-west-1.amazonaws.com" + const templateUrl = `https://${DEFAULT_TEMPLATES_BUCKET}/templates/${type}/${name}.tar.gz` + return objectStore.downloadTarball( + templateUrl, + ObjectStoreBuckets.TEMPLATES, + type + ) +} diff --git a/packages/server/src/utilities/index.ts b/packages/server/src/utilities/index.ts index 4e672124cd..822ac68631 100644 --- a/packages/server/src/utilities/index.ts +++ b/packages/server/src/utilities/index.ts @@ -1,6 +1,5 @@ import env from "../environment" -import { OBJ_STORE_DIRECTORY } from "../constants" -import { objectStore, context } from "@budibase/backend-core" +import { context } from "@budibase/backend-core" import { generateMetadataID } from "../db/utils" import { Document } from "@budibase/types" import stream from "stream" @@ -32,49 +31,6 @@ export function checkSlashesInUrl(url: string) { return url.replace(/(https?:\/\/)|(\/)+/g, "$1$2") } -/** - * Gets the address of the object store, depending on whether self hosted or in cloud. - * @return {string} The base URL of the object store (MinIO or S3). - */ -export function objectStoreUrl() { - if (env.SELF_HOSTED || env.MINIO_URL) { - // can use a relative url for this as all goes through the proxy (this is hosted in minio) - return OBJ_STORE_DIRECTORY - } else { - return env.CDN_URL - } -} - -/** - * 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 function clientLibraryPath(appId: string, version: string) { - if (env.isProd()) { - let url = `${objectStoreUrl()}/${objectStore.sanitizeKey( - appId - )}/budibase-client.js` - - // append app version to bust the cache - if (version) { - url += `?v=${version}` - } - return url - } else { - return `/api/assets/client` - } -} - -export function attachmentsRelativeURL(attachmentKey: string) { - return checkSlashesInUrl(`${objectStoreUrl()}/${attachmentKey}`) -} - export async function updateEntityMetadata( type: string, entityId: string, diff --git a/packages/server/src/utilities/plugins.ts b/packages/server/src/utilities/plugins.ts deleted file mode 100644 index b3a9d50fff..0000000000 --- a/packages/server/src/utilities/plugins.ts +++ /dev/null @@ -1,22 +0,0 @@ -import env from "../environment" -import { plugins as ProPlugins } from "@budibase/pro" -import { objectStore } from "@budibase/backend-core" -import { Plugin } from "@budibase/types" - -export function enrichPluginURLs(plugins: Plugin[]) { - if (!plugins || !plugins.length) { - return [] - } - return plugins.map(plugin => { - const cloud = !env.SELF_HOSTED - const bucket = objectStore.ObjectStoreBuckets.PLUGINS - const jsFileName = "plugin.min.js" - - // In self host we need to prefix the path, as the bucket name is not part - // of the bucket path. In cloud, it's already part of the bucket path. - let jsUrl = cloud ? `${env.CDN_URL}/` : `/${bucket}/` - jsUrl += ProPlugins.getBucketPath(plugin.name) - jsUrl += jsFileName - return { ...plugin, jsUrl } - }) -} diff --git a/packages/server/src/utilities/rowProcessor/index.ts b/packages/server/src/utilities/rowProcessor/index.ts index e519c261c5..9e1029fa1a 100644 --- a/packages/server/src/utilities/rowProcessor/index.ts +++ b/packages/server/src/utilities/rowProcessor/index.ts @@ -1,12 +1,11 @@ import * as linkRows from "../../db/linkedRows" import { FieldTypes, AutoFieldSubTypes } from "../../constants" -import { attachmentsRelativeURL } from "../index" import { processFormulas, fixAutoColumnSubType } from "./utils" import { ObjectStoreBuckets } from "../../constants" import { context, db as dbCore, objectStore } from "@budibase/backend-core" import { InternalTables } from "../../db/utils" import { TYPE_TRANSFORM_MAP } from "./map" -import { Row, Table, ContextUser } from "@budibase/types" +import { Row, RowAttachment, Table, ContextUser } from "@budibase/types" const { cloneDeep } = require("lodash/fp") export * from "./utils" @@ -35,7 +34,7 @@ function getRemovedAttachmentKeys( return oldKeys } const newKeys = row[attachmentKey].map((attachment: any) => attachment.key) - return oldKeys.filter((key: any) => newKeys.indexOf(key) === -1) + return oldKeys.filter((key: string) => newKeys.indexOf(key) === -1) } /** @@ -105,7 +104,7 @@ export function processAutoColumn( * @param {object} type The type fo coerce to * @returns {object} The coerced value */ -export function coerce(row: any, type: any) { +export function coerce(row: any, type: string) { // no coercion specified for type, skip it if (!TYPE_TRANSFORM_MAP[type]) { return row @@ -158,6 +157,16 @@ export function inputProcessing( else { clonedRow[key] = coerce(value, field.type) } + + // remove any attachment urls, they are generated on read + if (field.type === FieldTypes.ATTACHMENT) { + const attachments = clonedRow[key] + if (attachments?.length) { + attachments.forEach((attachment: RowAttachment) => { + delete attachment.url + }) + } + } } if (!clonedRow._id || !clonedRow._rev) { @@ -194,15 +203,15 @@ export async function outputProcessing( // process formulas enriched = processFormulas(table, enriched, { dynamic: true }) as Row[] - // update the attachments URL depending on hosting + // set the attachments URLs for (let [property, column] of Object.entries(table.schema)) { if (column.type === FieldTypes.ATTACHMENT) { for (let row of enriched) { if (row[property] == null || !Array.isArray(row[property])) { continue } - row[property].forEach((attachment: any) => { - attachment.url = attachmentsRelativeURL(attachment.key) + row[property].forEach((attachment: RowAttachment) => { + attachment.url = objectStore.getAppFileUrl(attachment.key) }) } } @@ -265,6 +274,6 @@ export async function cleanupAttachments( } } if (files.length > 0) { - return objectStore.deleteFiles(ObjectStoreBuckets.APPS, files) + await objectStore.deleteFiles(ObjectStoreBuckets.APPS, files) } } diff --git a/packages/server/src/utilities/rowProcessor/tests/utils.spec.ts b/packages/server/src/utilities/rowProcessor/tests/utils.spec.ts new file mode 100644 index 0000000000..a9ab59c15a --- /dev/null +++ b/packages/server/src/utilities/rowProcessor/tests/utils.spec.ts @@ -0,0 +1,57 @@ +import { fixAutoColumnSubType } from "../utils" +import { AutoFieldDefaultNames, AutoFieldSubTypes } from "../../../constants" + +describe("rowProcessor utility", () => { + describe("fixAutoColumnSubType", () => { + let schema = { + name: "", + type: "link", + subtype: "", // missing subtype + icon: "ri-magic-line", + autocolumn: true, + constraints: { type: "array", presence: false }, + tableId: "ta_users", + fieldName: "test-Updated By", + relationshipType: "many-to-many", + sortable: false, + } + + it("updates the schema with the correct subtype", async () => { + schema.name = AutoFieldDefaultNames.CREATED_BY + expect(fixAutoColumnSubType(schema).subtype).toEqual( + AutoFieldSubTypes.CREATED_BY + ) + schema.subtype = "" + + schema.name = AutoFieldDefaultNames.UPDATED_BY + expect(fixAutoColumnSubType(schema).subtype).toEqual( + AutoFieldSubTypes.UPDATED_BY + ) + schema.subtype = "" + + schema.name = AutoFieldDefaultNames.CREATED_AT + expect(fixAutoColumnSubType(schema).subtype).toEqual( + AutoFieldSubTypes.CREATED_AT + ) + schema.subtype = "" + + schema.name = AutoFieldDefaultNames.UPDATED_AT + expect(fixAutoColumnSubType(schema).subtype).toEqual( + AutoFieldSubTypes.UPDATED_AT + ) + schema.subtype = "" + + schema.name = AutoFieldDefaultNames.AUTO_ID + expect(fixAutoColumnSubType(schema).subtype).toEqual( + AutoFieldSubTypes.AUTO_ID + ) + schema.subtype = "" + }) + + it("returns the column if subtype exists", async () => { + schema.subtype = AutoFieldSubTypes.CREATED_BY + schema.name = AutoFieldDefaultNames.CREATED_AT + expect(fixAutoColumnSubType(schema)).toEqual(schema) + }) + }) +}) diff --git a/packages/server/tsconfig.build.json b/packages/server/tsconfig.build.json index 2212a5e100..212fc1479d 100644 --- a/packages/server/tsconfig.build.json +++ b/packages/server/tsconfig.build.json @@ -19,6 +19,8 @@ "node_modules", "dist", "src/tests", + "src/api/routes/tests/utilities", + "src/automations/tests/utilities", "**/*.spec.ts", "**/*.spec.js" ] diff --git a/packages/server/yarn.lock b/packages/server/yarn.lock index 8810a96820..d8e9bc2d54 100644 --- a/packages/server/yarn.lock +++ b/packages/server/yarn.lock @@ -1273,14 +1273,16 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@budibase/backend-core@2.1.43-alpha.15": - version "2.1.43-alpha.15" - resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.1.43-alpha.15.tgz#0c1d3ca30010fac5c4828ddac0bda38c0ed2e473" - integrity sha512-zUj/+q4mOGXTmElpJ3B9hBngKnw+qDGLp5wEMqQGSjmcBq/SOCJBpM1yHSqDbMWBJguSmshPMrEjGMe78r+BOQ== +"@budibase/backend-core@2.2.12-alpha.4": + version "2.2.12-alpha.4" + resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.2.12-alpha.4.tgz#02fec92bd744acda0a5e205c8b898eaa638a797f" + integrity sha512-62+eaj64wTFEC3XnHBan9sqW5XxQ3NAKyeu6n7H7ABFuk+a5g8Ym4VWbU7xgsyqVQOO4XzWzDjE0kF9e4ntGtg== dependencies: - "@budibase/types" "2.1.43-alpha.15" + "@budibase/nano" "10.1.1" + "@budibase/types" "2.2.12-alpha.4" "@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" @@ -1289,11 +1291,10 @@ 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" - nano "^10.1.0" node-fetch "2.6.7" passport-google-oauth "2.0.0" passport-jwt "4.0.0" @@ -1360,16 +1361,29 @@ svelte-flatpickr "^3.2.3" svelte-portal "^1.0.0" -"@budibase/pro@2.1.43-alpha.15": - version "2.1.43-alpha.15" - resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.1.43-alpha.15.tgz#2ad444af59a0f28794c0dcf3f1112e74b5b1eb1c" - integrity sha512-s7PNnWe9Mx5poF9+hgnTgY4B1zZqLlmJT9o7IY99uDf1sYCf6CRX4uJ+WG9+ROL/UcKPXtllZQuxompreyA8ow== +"@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: - "@budibase/backend-core" "2.1.43-alpha.15" - "@budibase/types" "2.1.43-alpha.15" + "@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" + +"@budibase/pro@2.2.12-alpha.4": + version "2.2.12-alpha.4" + resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.2.12-alpha.4.tgz#026899d3f333c8fd1788d80185d96aa3daba5d03" + integrity sha512-f/5dkyS9c8FEY6BoB6pjiDn7f1sg0xlkOEou6TPg/HhFZxeHi11OJHNdso46Eu0tyb4qylpk0TRFKzkhWM0cxQ== + dependencies: + "@budibase/backend-core" "2.2.12-alpha.4" + "@budibase/types" "2.2.12-alpha.4" "@koa/router" "8.0.8" bull "4.10.1" joi "17.6.0" + jsonwebtoken "8.5.1" node-fetch "^2.6.1" "@budibase/standard-components@^0.9.139": @@ -1390,10 +1404,10 @@ svelte-apexcharts "^1.0.2" svelte-flatpickr "^3.1.0" -"@budibase/types@2.1.43-alpha.15": - version "2.1.43-alpha.15" - resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.1.43-alpha.15.tgz#0d5b372d615f1f9509a606fb952f4eeabeda19c7" - integrity sha512-ATh/UyYGZ567UDcQo9/tuDa6SXm7a5WcXBMsIZVLlGSg56dECpiz7SIerr+sOtjtAgdHqIy4aYcQmgA5k9eWdg== +"@budibase/types@2.2.12-alpha.4": + version "2.2.12-alpha.4" + resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.2.12-alpha.4.tgz#4acc580dcc15bc400f193c175a750f249a7ae621" + integrity sha512-d77CNXwMUzOHiqmTBZxxQZkbQLehU9564UCIFdqkofFWkwHpaVtmmnGuuSfnvD5E3XwV43DEtVJWLMv9oaMw3Q== "@bull-board/api@3.7.0": version "3.7.0" @@ -3124,10 +3138,10 @@ "@types/koa-compose" "*" "@types/node" "*" -"@types/koa__router@8.0.11": - version "8.0.11" - resolved "https://registry.yarnpkg.com/@types/koa__router/-/koa__router-8.0.11.tgz#d7b37e6db934fc072ea1baa2ab92bc8ac4564f3e" - integrity sha512-WXgKWpBsbS14kzmzD9LeFapOIa678h7zvUHxDwXwSx4ETKXhXLVUAToX6jZ/U7EihM7qwyD9W/BZvB0MRu7MTQ== +"@types/koa__router@8.0.8": + version "8.0.8" + resolved "https://registry.yarnpkg.com/@types/koa__router/-/koa__router-8.0.8.tgz#b1e0e9a512498777d3366bbdf0e853df27ec831c" + integrity sha512-9pGCaDtzCsj4HJ8HmGuqzk8+s57sPj4njWd08GG5o92n5Xp9io2snc40CPpXFhoKcZ8OKhuu6ht4gNou9e1C2w== dependencies: "@types/koa" "*" @@ -3399,6 +3413,13 @@ "@types/mime" "^1" "@types/node" "*" +"@types/server-destroy@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/server-destroy/-/server-destroy-1.0.1.tgz#6010a89e2df4f2c15a265fe73c70fd3641486530" + integrity sha512-77QGr7waZbE0Y0uF+G+uH3H3SmhyA78Jf2r5r7QSrpg0U3kSXduWpGjzP9PvPLR/KCy+kHjjpnugRHsYTnHopg== + dependencies: + "@types/node" "*" + "@types/stack-utils@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" @@ -3417,6 +3438,14 @@ "@types/cookiejar" "*" "@types/node" "*" +"@types/tar@6.1.3": + version "6.1.3" + resolved "https://registry.yarnpkg.com/@types/tar/-/tar-6.1.3.tgz#46a2ce7617950c4852dfd7e9cd41aa8161b9d750" + integrity sha512-YzDOr5kdAeqS8dcO6NTTHTMJ44MUCBDoLEIyPtwEn7PssKqUYL49R1iCVJPeiPzPlKi6DbH33eZkpeJ27e4vHg== + dependencies: + "@types/node" "*" + minipass "^3.3.5" + "@types/tough-cookie@*", "@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" @@ -4217,6 +4246,13 @@ available-typed-arrays@^1.0.5: resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== +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" @@ -9564,6 +9600,16 @@ jsonwebtoken@8.5.1, jsonwebtoken@^8.2.0, jsonwebtoken@^8.5.1: ms "^2.1.1" semver "^5.6.0" +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" + jsprim@^1.2.2: version "1.4.2" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" @@ -10197,6 +10243,11 @@ lodash@4.17.21, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17. 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== + logform@^2.3.2, logform@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/logform/-/logform-2.4.0.tgz#131651715a17d50f09c2a2c1a524ff1a4164bcfe" @@ -10261,9 +10312,9 @@ ltgt@2.2.1, ltgt@^2.1.2, ltgt@~2.2.0: integrity sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA== luxon@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.0.1.tgz#6901111d10ad06fd267ad4e4128a84bef8a77299" - integrity sha512-hF3kv0e5gwHQZKz4wtm4c+inDtyc7elkanAsBq+fundaCdUBNJB1dHEGUZIM6SfSBUlbVFduPwEtNjFK8wLtcw== + 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@^1.0.0: version "1.3.0" @@ -10534,6 +10585,13 @@ minipass@^3.0.0: dependencies: yallist "^4.0.0" +minipass@^3.3.5: + 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" + minizlib@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" @@ -10720,18 +10778,6 @@ nan@^2.12.1: resolved "https://registry.yarnpkg.com/nan/-/nan-2.16.0.tgz#664f43e45460fb98faf00edca0bb0d7b8dce7916" integrity sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA== -nano@^10.1.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/nano/-/nano-10.1.0.tgz#afdd5a7440e62f09a8e23f41fcea328d27383922" - integrity sha512-COeN2TpLcHuSN44QLnPmfZCoCsKAg8/aelPOVqqm/2/MvRHDEA11/Kld5C4sLzDlWlhFZ3SO2WGJGevCsvcEzQ== - 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" - nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -12936,7 +12982,7 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.3.7: +semver@^7.3.7, semver@^7.3.8: version "7.3.8" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index 01fd8092c3..1229f0ecbb 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/string-templates", - "version": "2.1.43-alpha.15", + "version": "2.2.12-alpha.4", "description": "Handlebars wrapper for Budibase templating.", "main": "src/index.cjs", "module": "dist/bundle.mjs", diff --git a/packages/string-templates/yarn.lock b/packages/string-templates/yarn.lock index 5665d8eb02..4488e58d46 100644 --- a/packages/string-templates/yarn.lock +++ b/packages/string-templates/yarn.lock @@ -2874,11 +2874,9 @@ json-parse-even-better-errors@^2.3.0: integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== json5@^2.1.2: - version "2.2.0" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" - integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== - dependencies: - minimist "^1.2.5" + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.1.0, kind-of@^3.2.0: version "3.2.2" diff --git a/packages/types/package.json b/packages/types/package.json index c7fe498a26..195b491af9 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/types", - "version": "2.1.43-alpha.15", + "version": "2.2.12-alpha.4", "description": "Budibase types", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -13,11 +13,12 @@ }, "jest": {}, "devDependencies": { - "@types/json5": "^2.2.0", + "@budibase/nano": "10.1.1", + "@types/json5": "2.2.0", "@types/koa": "2.13.4", "@types/node": "14.18.20", "@types/pouchdb": "6.4.0", - "nano": "10.1.0", + "koa-body": "4.2.0", "rimraf": "3.0.2", "typescript": "4.7.3" } diff --git a/packages/types/src/documents/account/account.ts b/packages/types/src/documents/account/account.ts index 70c3061c3f..a8684f8427 100644 --- a/packages/types/src/documents/account/account.ts +++ b/packages/types/src/documents/account/account.ts @@ -1,8 +1,10 @@ import { Feature, Hosting, + License, MonthlyQuotaName, PlanType, + PriceDuration, Quotas, StaticQuotaName, } from "../../sdk" @@ -46,6 +48,7 @@ export interface Account extends CreateAccount { tier: string // deprecated planType?: PlanType planTier?: number + license?: License stripeCustomerId?: string licenseKey?: string licenseKeyActivatedAt?: number diff --git a/packages/types/src/documents/app/row.ts b/packages/types/src/documents/app/row.ts index 2cac32279b..b18c6ebee7 100644 --- a/packages/types/src/documents/app/row.ts +++ b/packages/types/src/documents/app/row.ts @@ -19,9 +19,10 @@ export enum FieldType { export interface RowAttachment { size: number name: string - url: string extension: string key: string + // Populated on read + url?: string } export interface Row extends Document { diff --git a/packages/types/src/documents/global/config.ts b/packages/types/src/documents/global/config.ts index 65a8b15589..9b05069c9a 100644 --- a/packages/types/src/documents/global/config.ts +++ b/packages/types/src/documents/global/config.ts @@ -2,6 +2,7 @@ import { Document } from "../document" export interface Config extends Document { type: ConfigType + config: any } export interface SMTPConfig extends Config { @@ -17,9 +18,12 @@ export interface SMTPConfig extends Config { export interface SettingsConfig extends Config { config: { company: string - logoUrl: string + // Populated on read + logoUrl?: string + logoUrlEtag?: boolean platformUrl: string uniqueTenantId?: string + analyticsEnabled?: boolean } } @@ -57,18 +61,15 @@ export interface OIDCConfig extends Config { } } -export type NestedConfig = - | SMTPConfig - | SettingsConfig - | GoogleConfig - | OIDCConfig - export const isSettingsConfig = (config: Config): config is SettingsConfig => config.type === ConfigType.SETTINGS + export const isSMTPConfig = (config: Config): config is SMTPConfig => config.type === ConfigType.SMTP + export const isGoogleConfig = (config: Config): config is GoogleConfig => config.type === ConfigType.GOOGLE + export const isOIDCConfig = (config: Config): config is OIDCConfig => config.type === ConfigType.OIDC diff --git a/packages/types/src/documents/global/plugin.ts b/packages/types/src/documents/global/plugin.ts index 7f6d4f4995..17a8426d85 100644 --- a/packages/types/src/documents/global/plugin.ts +++ b/packages/types/src/documents/global/plugin.ts @@ -20,8 +20,6 @@ export interface Plugin extends Document { description: string name: string version: string - jsUrl?: string - iconUrl?: string source: PluginSource package: { [key: string]: any } hash: string @@ -29,6 +27,11 @@ export interface Plugin extends Document { type: PluginType [key: string]: any } + iconFileName?: string + // Populated on read + jsUrl?: string + // Populated on read + iconUrl?: string } export const PLUGIN_TYPE_ARR = Object.values(PluginType) diff --git a/packages/types/src/sdk/db.ts b/packages/types/src/sdk/db.ts index 84daed0f90..50078ed6cc 100644 --- a/packages/types/src/sdk/db.ts +++ b/packages/types/src/sdk/db.ts @@ -1,5 +1,5 @@ import PouchDB from "pouchdb" -import Nano from "nano" +import Nano from "@budibase/nano" import { AllDocsResponse, AnyDocument, Document } from "../" export type PouchOptions = { diff --git a/packages/types/src/sdk/events/auth.ts b/packages/types/src/sdk/events/auth.ts index 6d79fb344b..eb9f3148a3 100644 --- a/packages/types/src/sdk/events/auth.ts +++ b/packages/types/src/sdk/events/auth.ts @@ -1,7 +1,8 @@ import { BaseEvent } from "./event" +import { ConfigType } from "../../documents" export type LoginSource = "local" | "google" | "oidc" | "google-internal" -export type SSOType = "oidc" | "google" +export type SSOType = ConfigType.OIDC | ConfigType.GOOGLE export interface LoginEvent extends BaseEvent { userId: string diff --git a/packages/types/src/sdk/koa.ts b/packages/types/src/sdk/koa.ts index 07d4a893c1..302d1aeb67 100644 --- a/packages/types/src/sdk/koa.ts +++ b/packages/types/src/sdk/koa.ts @@ -1,6 +1,7 @@ import { Context, Request } from "koa" import { User, Role, UserRoles, Account } from "../documents" import { FeatureFlag, License } from "../sdk" +import { Files } from "formidable" export interface ContextUser extends Omit { globalId?: string @@ -15,12 +16,34 @@ export interface ContextUser extends Omit { account?: Account } -export interface BBRequest extends Request { - body: any - files?: any +/** + * Add support for koa-body in context. + */ +export interface BBRequest extends Request { + body: RequestBody + files?: Files } -export interface BBContext extends Context { - request: BBRequest +/** + * Basic context with no user. + */ +export interface Ctx extends Context { + request: BBRequest + body: ResponseBody +} + +/** + * Authenticated context. + */ +export interface UserCtx + extends Ctx { + user: ContextUser +} + +/** + * Deprecated: Use UserCtx / Ctx appropriately + * Authenticated context. + */ +export interface BBContext extends Ctx { user?: ContextUser } diff --git a/packages/types/src/sdk/locks.ts b/packages/types/src/sdk/locks.ts index e2f1902762..e6809319b1 100644 --- a/packages/types/src/sdk/locks.ts +++ b/packages/types/src/sdk/locks.ts @@ -4,11 +4,14 @@ export enum LockType { * No retries will take place and no error will be thrown. */ TRY_ONCE = "try_once", + DEFAULT = "default", + DELAY_500 = "delay_500", } export enum LockName { MIGRATIONS = "migrations", TRIGGER_QUOTA = "trigger_quota", + SYNC_ACCOUNT_LICENSE = "sync_account_license", } export interface LockOptions { diff --git a/packages/types/yarn.lock b/packages/types/yarn.lock index f596c58997..4b45eb90ed 100644 --- a/packages/types/yarn.lock +++ b/packages/types/yarn.lock @@ -2,6 +2,18 @@ # yarn lockfile v1 +"@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" + "@types/accepts@*": version "1.3.5" resolved "https://registry.yarnpkg.com/@types/accepts/-/accepts-1.3.5.tgz#c34bec115cfc746e04fe5a059df4ce7e7b391575" @@ -65,6 +77,13 @@ "@types/qs" "*" "@types/serve-static" "*" +"@types/formidable@^1.0.31": + version "1.2.5" + resolved "https://registry.yarnpkg.com/@types/formidable/-/formidable-1.2.5.tgz#561d026e5f09179e5c8ef7b31e8f4652e11abe4c" + integrity sha512-zu3mQJa4hDNubEMViSj937602XdDGzK7Q5pJ5QmLUbNxclbo9tZGt5jtwM352ssZ+pqo5V4H14TBvT/ALqQQcA== + dependencies: + "@types/node" "*" + "@types/http-assert@*": version "1.5.3" resolved "https://registry.yarnpkg.com/@types/http-assert/-/http-assert-1.5.3.tgz#ef8e3d1a8d46c387f04ab0f2e8ab8cb0c5078661" @@ -75,7 +94,7 @@ resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-1.8.2.tgz#7315b4c4c54f82d13fa61c228ec5c2ea5cc9e0e1" integrity sha512-EqX+YQxINb+MeXaIqYDASb6U6FCHbWjkj4a1CKDBks3d/QiB2+PqBLyO72vLDgAO1wUI4O+9gweRcQK11bTL/w== -"@types/json5@^2.2.0": +"@types/json5@2.2.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-2.2.0.tgz#afff29abf9182a7d4a7e39105ca051f11c603d13" integrity sha512-NrVug5woqbvNZ0WX+Gv4R+L4TGddtmFek2u8RtccAgFZWtS9QXF2xCXY22/M4nzkaKF0q9Fc6M/5rxLDhfwc/A== @@ -345,6 +364,11 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +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: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -353,6 +377,16 @@ call-bind@^1.0.0: function-bind "^1.1.1" get-intrinsic "^1.0.2" +co-body@^5.1.1: + version "5.2.0" + resolved "https://registry.yarnpkg.com/co-body/-/co-body-5.2.0.tgz#5a0a658c46029131e0e3a306f67647302f71c124" + integrity sha512-sX/LQ7LqUhgyaxzbe7IqwPeTr2yfpfUIQ/dgpKo6ZI4y4lpQA0YxAomWIY+7I7rHWcG02PG+OuPREzMW/5tszQ== + dependencies: + inflation "^2.0.0" + qs "^6.4.0" + raw-body "^2.2.0" + type-is "^1.6.14" + combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -377,6 +411,11 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== +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== + follow-redirects@^1.15.0: version "1.15.2" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" @@ -391,6 +430,11 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +formidable@^1.1.1: + version "1.2.6" + resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.6.tgz#d2a51d60162bbc9b4a055d8457a7c75315d1a168" + integrity sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ== + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -441,6 +485,29 @@ http-cookie-agent@^4.0.2: dependencies: agent-base "^6.0.2" +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" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +inflation@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/inflation/-/inflation-2.0.0.tgz#8b417e47c28f925a45133d914ca1fd389107f30f" + integrity sha512-m3xv4hJYR2oXw4o4Y5l6P5P16WYmazYof+el6Al3f+YlggGj6qT9kImBAnzDelRALnP5d3h4jGBPKzYCizjZZw== + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -449,22 +516,36 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2: +inherits@2, inherits@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== json5@*: - version "2.2.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" - integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +koa-body@4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/koa-body/-/koa-body-4.2.0.tgz#37229208b820761aca5822d14c5fc55cee31b26f" + integrity sha512-wdGu7b9amk4Fnk/ytH8GuWwfs4fsB5iNkY8kZPpgQVb04QZSv85T0M8reb+cJmvLE8cjPYvBzRikD3s6qz8OoA== + dependencies: + "@types/formidable" "^1.0.31" + co-body "^5.1.1" + formidable "^1.1.1" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== mime-db@1.52.0: version "1.52.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12: +mime-types@^2.1.12, mime-types@~2.1.24: 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== @@ -483,18 +564,6 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -nano@10.1.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/nano/-/nano-10.1.0.tgz#afdd5a7440e62f09a8e23f41fcea328d27383922" - integrity sha512-COeN2TpLcHuSN44QLnPmfZCoCsKAg8/aelPOVqqm/2/MvRHDEA11/Kld5C4sLzDlWlhFZ3SO2WGJGevCsvcEzQ== - 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" - 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" @@ -532,7 +601,7 @@ punycode@^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.11.0: +qs@^6.11.0, qs@^6.4.0: version "6.11.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== @@ -544,6 +613,16 @@ querystringify@^2.1.1: resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== +raw-body@^2.2.0: + 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.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" @@ -556,6 +635,16 @@ rimraf@3.0.2: dependencies: glob "^7.1.3" +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" @@ -565,6 +654,16 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.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== + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + tough-cookie@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874" @@ -575,6 +674,14 @@ tough-cookie@^4.1.2: universalify "^0.2.0" url-parse "^1.5.3" +type-is@^1.6.14: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + typescript@4.7.3: version "4.7.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.3.tgz#8364b502d5257b540f9de4c40be84c98e23a129d" @@ -585,6 +692,11 @@ universalify@^0.2.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== +unpipe@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + url-parse@^1.5.3: version "1.5.10" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" diff --git a/packages/worker/jest.config.ts b/packages/worker/jest.config.ts index d8a2d59722..c482dffd38 100644 --- a/packages/worker/jest.config.ts +++ b/packages/worker/jest.config.ts @@ -19,6 +19,8 @@ if (!process.env.CI) { } // add pro sources if they exist if (fs.existsSync("../../../budibase-pro")) { + config.moduleNameMapper["@budibase/pro/(.*)"] = + "/../../../budibase-pro/packages/pro/$1" config.moduleNameMapper["@budibase/pro"] = "/../../../budibase-pro/packages/pro/src" } diff --git a/packages/worker/package.json b/packages/worker/package.json index a155a6546c..cbdabf26f2 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/worker", "email": "hi@budibase.com", - "version": "2.1.43-alpha.15", + "version": "2.2.12-alpha.4", "description": "Budibase background service", "main": "src/index.ts", "repository": { @@ -36,10 +36,10 @@ "author": "Budibase", "license": "GPL-3.0", "dependencies": { - "@budibase/backend-core": "2.1.43-alpha.15", - "@budibase/pro": "2.1.43-alpha.15", - "@budibase/string-templates": "2.1.43-alpha.15", - "@budibase/types": "2.1.43-alpha.15", + "@budibase/backend-core": "2.2.12-alpha.4", + "@budibase/pro": "2.2.12-alpha.4", + "@budibase/string-templates": "2.2.12-alpha.4", + "@budibase/types": "2.2.12-alpha.4", "@koa/router": "8.0.8", "@sentry/node": "6.17.7", "@techpass/passport-openidconnect": "0.3.2", @@ -73,10 +73,11 @@ "@types/jest": "26.0.23", "@types/jsonwebtoken": "8.5.1", "@types/koa": "2.13.4", - "@types/koa__router": "8.0.11", + "@types/koa__router": "8.0.8", "@types/node": "14.18.20", "@types/node-fetch": "2.6.1", "@types/pouchdb": "6.4.0", + "@types/server-destroy": "1.0.1", "@types/uuid": "8.3.4", "@typescript-eslint/parser": "5.45.0", "copyfiles": "2.4.1", diff --git a/packages/worker/src/api/controllers/global/configs.ts b/packages/worker/src/api/controllers/global/configs.ts index 1c4e57f0e1..89c67180bc 100644 --- a/packages/worker/src/api/controllers/global/configs.ts +++ b/packages/worker/src/api/controllers/global/configs.ts @@ -7,6 +7,7 @@ import { objectStore, tenancy, db as dbCore, + env as coreEnv, } from "@budibase/backend-core" import { checkAnyUserExists } from "../../../utilities/users" import { @@ -17,127 +18,106 @@ import { GoogleConfig, OIDCConfig, SettingsConfig, - BBContext, + isGoogleConfig, + isOIDCConfig, + isSettingsConfig, + isSMTPConfig, + Ctx, + UserCtx, } from "@budibase/types" const getEventFns = async (db: Database, config: ConfigDoc) => { const fns = [] - const type = config.type let existing if (config._id) { existing = await db.get(config._id) } - const ssoType = type as SSOType if (!existing) { - switch (config.type) { - case ConfigType.SMTP: { - fns.push(events.email.SMTPCreated) - break + if (isSMTPConfig(config)) { + fns.push(events.email.SMTPCreated) + } else if (isGoogleConfig(config)) { + fns.push(() => events.auth.SSOCreated(ConfigType.GOOGLE)) + if (config.config.activated) { + fns.push(() => events.auth.SSOActivated(ConfigType.GOOGLE)) } - case ConfigType.GOOGLE: { - const googleCfg = config as GoogleConfig - fns.push(() => events.auth.SSOCreated(ssoType)) - if (googleCfg.config.activated) { - fns.push(() => events.auth.SSOActivated(ssoType)) - } - break + } else if (isOIDCConfig(config)) { + fns.push(() => events.auth.SSOCreated(ConfigType.OIDC)) + if (config.config.configs[0].activated) { + fns.push(() => events.auth.SSOActivated(ConfigType.OIDC)) } - case ConfigType.OIDC: { - const oidcCfg = config as OIDCConfig - fns.push(() => events.auth.SSOCreated(ssoType)) - if (oidcCfg.config.configs[0].activated) { - fns.push(() => events.auth.SSOActivated(ssoType)) - } - break + } else if (isSettingsConfig(config)) { + // company + const company = config.config.company + if (company && company !== "Budibase") { + fns.push(events.org.nameUpdated) } - case ConfigType.SETTINGS: { - // company - const settingsCfg = config as SettingsConfig - const company = settingsCfg.config.company - if (company && company !== "Budibase") { - fns.push(events.org.nameUpdated) - } - // logo - const logoUrl = settingsCfg.config.logoUrl - if (logoUrl) { - fns.push(events.org.logoUpdated) - } + // logo + const logoUrl = config.config.logoUrl + if (logoUrl) { + fns.push(events.org.logoUpdated) + } - // platform url - const platformUrl = settingsCfg.config.platformUrl - if ( - platformUrl && - platformUrl !== "http://localhost:10000" && - env.SELF_HOSTED - ) { - fns.push(events.org.platformURLUpdated) - } - break + // platform url + const platformUrl = config.config.platformUrl + if ( + platformUrl && + platformUrl !== "http://localhost:10000" && + env.SELF_HOSTED + ) { + fns.push(events.org.platformURLUpdated) } } } else { - switch (config.type) { - case ConfigType.SMTP: { - fns.push(events.email.SMTPUpdated) - break + if (isSMTPConfig(config)) { + fns.push(events.email.SMTPUpdated) + } else if (isGoogleConfig(config)) { + fns.push(() => events.auth.SSOUpdated(ConfigType.GOOGLE)) + if (!existing.config.activated && config.config.activated) { + fns.push(() => events.auth.SSOActivated(ConfigType.GOOGLE)) + } else if (existing.config.activated && !config.config.activated) { + fns.push(() => events.auth.SSODeactivated(ConfigType.GOOGLE)) } - case ConfigType.GOOGLE: { - const googleCfg = config as GoogleConfig - fns.push(() => events.auth.SSOUpdated(ssoType)) - if (!existing.config.activated && googleCfg.config.activated) { - fns.push(() => events.auth.SSOActivated(ssoType)) - } else if (existing.config.activated && !googleCfg.config.activated) { - fns.push(() => events.auth.SSODeactivated(ssoType)) - } - break + } else if (isOIDCConfig(config)) { + fns.push(() => events.auth.SSOUpdated(ConfigType.OIDC)) + if ( + !existing.config.configs[0].activated && + config.config.configs[0].activated + ) { + fns.push(() => events.auth.SSOActivated(ConfigType.OIDC)) + } else if ( + existing.config.configs[0].activated && + !config.config.configs[0].activated + ) { + fns.push(() => events.auth.SSODeactivated(ConfigType.OIDC)) } - case ConfigType.OIDC: { - const oidcCfg = config as OIDCConfig - fns.push(() => events.auth.SSOUpdated(ssoType)) - if ( - !existing.config.configs[0].activated && - oidcCfg.config.configs[0].activated - ) { - fns.push(() => events.auth.SSOActivated(ssoType)) - } else if ( - existing.config.configs[0].activated && - !oidcCfg.config.configs[0].activated - ) { - fns.push(() => events.auth.SSODeactivated(ssoType)) - } - break + } else if (isSettingsConfig(config)) { + // company + const existingCompany = existing.config.company + const company = config.config.company + if (company && company !== "Budibase" && existingCompany !== company) { + fns.push(events.org.nameUpdated) } - case ConfigType.SETTINGS: { - // company - const settingsCfg = config as SettingsConfig - const existingCompany = existing.config.company - const company = settingsCfg.config.company - if (company && company !== "Budibase" && existingCompany !== company) { - fns.push(events.org.nameUpdated) - } - // logo - const existingLogoUrl = existing.config.logoUrl - const logoUrl = settingsCfg.config.logoUrl - if (logoUrl && existingLogoUrl !== logoUrl) { - fns.push(events.org.logoUpdated) - } + // logo + const existingLogoUrl = existing.config.logoUrl + const logoUrl = config.config.logoUrl + if (logoUrl && existingLogoUrl !== logoUrl) { + fns.push(events.org.logoUpdated) + } - // platform url - const existingPlatformUrl = existing.config.platformUrl - const platformUrl = settingsCfg.config.platformUrl - if ( - platformUrl && - platformUrl !== "http://localhost:10000" && - existingPlatformUrl !== platformUrl && - env.SELF_HOSTED - ) { - fns.push(events.org.platformURLUpdated) - } - break + // platform url + const existingPlatformUrl = existing.config.platformUrl + const platformUrl = config.config.platformUrl + if ( + platformUrl && + platformUrl !== "http://localhost:10000" && + existingPlatformUrl !== platformUrl && + env.SELF_HOSTED + ) { + fns.push(events.org.platformURLUpdated) } } } @@ -145,7 +125,7 @@ const getEventFns = async (db: Database, config: ConfigDoc) => { return fns } -export async function save(ctx: BBContext) { +export async function save(ctx: UserCtx) { const db = tenancy.getGlobalDB() const { type, workspace, user, config } = ctx.request.body let eventFns = await getEventFns(db, ctx.request.body) @@ -187,7 +167,7 @@ export async function save(ctx: BBContext) { } } -export async function fetch(ctx: BBContext) { +export async function fetch(ctx: UserCtx) { const db = tenancy.getGlobalDB() const response = await db.allDocs( dbCore.getConfigParams( @@ -204,7 +184,7 @@ export async function fetch(ctx: BBContext) { * Gets the most granular config for a particular configuration type. * The hierarchy is type -> workspace -> user. */ -export async function find(ctx: BBContext) { +export async function find(ctx: UserCtx) { const db = tenancy.getGlobalDB() const { userId, workspaceId } = ctx.query @@ -237,18 +217,18 @@ export async function find(ctx: BBContext) { } } -export async function publicOidc(ctx: BBContext) { +export async function publicOidc(ctx: Ctx) { const db = tenancy.getGlobalDB() try { // Find the config with the most granular scope based on context - const oidcConfig = await dbCore.getScopedFullConfig(db, { + const oidcConfig: OIDCConfig = await dbCore.getScopedFullConfig(db, { type: ConfigType.OIDC, }) if (!oidcConfig) { ctx.body = {} } else { - ctx.body = oidcConfig.config.configs.map((config: any) => ({ + ctx.body = oidcConfig.config.configs.map(config => ({ logo: config.logo, name: config.name, uuid: config.uuid, @@ -259,7 +239,7 @@ export async function publicOidc(ctx: BBContext) { } } -export async function publicSettings(ctx: BBContext) { +export async function publicSettings(ctx: Ctx) { const db = tenancy.getGlobalDB() try { @@ -285,6 +265,16 @@ export async function publicSettings(ctx: BBContext) { config = publicConfig } + // enrich the logo url + // empty url means deleted + if (config.config.logoUrl !== "") { + config.config.logoUrl = objectStore.getGlobalFileUrl( + "settings", + "logoUrl", + config.config.logoUrlEtag + ) + } + // google button flag if (googleConfig && googleConfig.config) { // activated by default for configs pre-activated flag @@ -311,28 +301,17 @@ export async function publicSettings(ctx: BBContext) { } } -export async function upload(ctx: BBContext) { - if (ctx.request.files == null || ctx.request.files.file.length > 1) { +export async function upload(ctx: UserCtx) { + if (ctx.request.files == null || Array.isArray(ctx.request.files.file)) { ctx.throw(400, "One file must be uploaded.") } - const file = ctx.request.files.file + const file = ctx.request.files.file as any const { type, name } = ctx.params - let bucket - if (env.SELF_HOSTED) { - bucket = objectStore.ObjectStoreBuckets.GLOBAL - } else { - bucket = objectStore.ObjectStoreBuckets.GLOBAL_CLOUD - } + let bucket = coreEnv.GLOBAL_BUCKET_NAME + const key = objectStore.getGlobalFileS3Key(type, name) - let key - if (env.MULTI_TENANCY) { - key = `${tenancy.getTenantId()}/${type}/${name}` - } else { - key = `${type}/${name}` - } - - await objectStore.upload({ + const result = await objectStore.upload({ bucket, filename: key, path: file.path, @@ -349,24 +328,26 @@ export async function upload(ctx: BBContext) { config: {}, } } - let url - if (env.SELF_HOSTED) { - url = `/${bucket}/${key}` - } else { - url = `${env.CDN_URL}/${key}` + + // save the Etag for cache bursting + const etag = result.ETag + if (etag) { + cfgStructure.config[`${name}Etag`] = etag.replace(/"/g, "") } - cfgStructure.config[`${name}`] = url - // write back to db with url updated + // save the file key + cfgStructure.config[`${name}`] = key + + // write back to db await db.put(cfgStructure) ctx.body = { message: "File has been uploaded and url stored to config.", - url, + url: objectStore.getGlobalFileUrl(type, name, etag), } } -export async function destroy(ctx: BBContext) { +export async function destroy(ctx: UserCtx) { const db = tenancy.getGlobalDB() const { id, rev } = ctx.params try { @@ -378,7 +359,7 @@ export async function destroy(ctx: BBContext) { } } -export async function configChecklist(ctx: BBContext) { +export async function configChecklist(ctx: Ctx) { const db = tenancy.getGlobalDB() const tenantId = tenancy.getTenantId() diff --git a/packages/worker/src/api/controllers/global/users.ts b/packages/worker/src/api/controllers/global/users.ts index 27b90cbd56..10741f3725 100644 --- a/packages/worker/src/api/controllers/global/users.ts +++ b/packages/worker/src/api/controllers/global/users.ts @@ -178,7 +178,7 @@ export const find = async (ctx: any) => { export const tenantUserLookup = async (ctx: any) => { const id = ctx.params.id - const user = await tenancy.getTenantUser(id) + const user = await sdk.users.getPlatformUser(id) if (user) { ctx.body = user } else { diff --git a/packages/worker/src/api/routes/global/license.ts b/packages/worker/src/api/routes/global/license.ts index 17b0d59e70..0fb2a6e8bd 100644 --- a/packages/worker/src/api/routes/global/license.ts +++ b/packages/worker/src/api/routes/global/license.ts @@ -10,4 +10,4 @@ router .delete("/api/global/license/info", controller.deleteInfo) .get("/api/global/license/usage", controller.getQuotaUsage) -export = router +export default router diff --git a/packages/worker/src/api/routes/global/tests/groups.spec.ts b/packages/worker/src/api/routes/global/tests/groups.spec.ts new file mode 100644 index 0000000000..9e136e58d9 --- /dev/null +++ b/packages/worker/src/api/routes/global/tests/groups.spec.ts @@ -0,0 +1,58 @@ +import { events } from "@budibase/backend-core" +import { structures, TestConfiguration, mocks } from "../../../../tests" + +describe("/api/global/groups", () => { + const config = new TestConfiguration() + + beforeAll(async () => { + await config.beforeAll() + }) + + afterAll(async () => { + await config.afterAll() + }) + + beforeEach(async () => { + mocks.licenses.useGroups() + }) + + describe("create", () => { + it("should be able to create a new group", async () => { + const group = structures.groups.UserGroup() + await config.api.groups.saveGroup(group) + expect(events.group.created).toBeCalledTimes(1) + expect(events.group.updated).not.toBeCalled() + expect(events.group.permissionsEdited).not.toBeCalled() + }) + }) + + describe("update", () => { + it("should be able to update a basic group", async () => { + const group = structures.groups.UserGroup() + let oldGroup = await config.api.groups.saveGroup(group) + + let updatedGroup = { + ...oldGroup.body, + ...group, + name: "New Name", + } + await config.api.groups.saveGroup(updatedGroup) + + expect(events.group.updated).toBeCalledTimes(1) + expect(events.group.permissionsEdited).not.toBeCalled() + }) + + describe("destroy", () => { + it("should be able to delete a basic group", async () => { + const group = structures.groups.UserGroup() + let oldGroup = await config.api.groups.saveGroup(group) + await config.api.groups.deleteGroup( + oldGroup.body._id, + oldGroup.body._rev + ) + + expect(events.group.deleted).toBeCalledTimes(1) + }) + }) + }) +}) diff --git a/packages/worker/src/api/routes/global/tests/license.spec.ts b/packages/worker/src/api/routes/global/tests/license.spec.ts index b25b41adb9..be0673729e 100644 --- a/packages/worker/src/api/routes/global/tests/license.spec.ts +++ b/packages/worker/src/api/routes/global/tests/license.spec.ts @@ -1,7 +1,5 @@ import { TestConfiguration } from "../../../../tests" -// TODO - describe("/api/global/license", () => { const config = new TestConfiguration() diff --git a/packages/worker/src/api/routes/global/tests/roles.spec.ts b/packages/worker/src/api/routes/global/tests/roles.spec.ts index 516c3433ab..2289273488 100644 --- a/packages/worker/src/api/routes/global/tests/roles.spec.ts +++ b/packages/worker/src/api/routes/global/tests/roles.spec.ts @@ -1,11 +1,47 @@ -import { TestConfiguration } from "../../../../tests" +import { structures, TestConfiguration } from "../../../../tests" +import { context, db, permissions, roles } from "@budibase/backend-core" +import { Mock } from "jest-mock" -// TODO +jest.mock("@budibase/backend-core", () => { + const core = jest.requireActual("@budibase/backend-core") + return { + ...core, + db: { + ...core.db, + }, + context: { + ...core.context, + getAppDB: jest.fn(), + }, + } +}) + +const appDb = db.getDB("app_test") +const mockAppDB = context.getAppDB as Mock +mockAppDB.mockReturnValue(appDb) + +async function addAppMetadata() { + await appDb.put({ + _id: "app_metadata", + appId: "app_test", + name: "New App", + version: "version", + url: "url", + }) +} describe("/api/global/roles", () => { const config = new TestConfiguration() + const role = new roles.Role( + db.generateRoleID("newRole"), + roles.BUILTIN_ROLE_IDS.BASIC, + permissions.BuiltinPermissionID.READ_ONLY + ) beforeAll(async () => { + console.debug(role) + appDb.put(role) + await addAppMetadata() await config.beforeAll() }) @@ -18,10 +54,35 @@ describe("/api/global/roles", () => { }) describe("GET /api/global/roles", () => { - it("retrieves roles", () => {}) + it("retrieves roles", async () => { + const res = await config.api.roles.get() + expect(res.body).toBeDefined() + expect(res.body["app_test"].roles.length).toEqual(5) + expect(res.body["app_test"].roles.map((r: any) => r._id)).toContain( + role._id + ) + }) }) - describe("GET /api/global/roles/:appId", () => {}) + describe("GET api/global/roles/:appId", () => { + it("finds a role by appId", async () => { + const res = await config.api.roles.find("app_test") + expect(res.body).toBeDefined() + expect(res.body.name).toEqual("New App") + }) + }) - describe("DELETE /api/global/roles/:appId", () => {}) + describe("DELETE /api/global/roles/:appId", () => { + it("removes an app role", async () => { + let user = structures.users.user() + user.roles = { + app_test: "role1", + } + const userResponse = await config.createUser(user) + const res = await config.api.roles.remove("app_test") + const updatedUser = await config.api.users.getUser(userResponse._id!) + expect(updatedUser.body.roles).not.toHaveProperty("app_test") + expect(res.body.message).toEqual("App role removed from all users") + }) + }) }) diff --git a/packages/worker/src/api/routes/global/tests/templates.spec.ts b/packages/worker/src/api/routes/global/tests/templates.spec.ts index d1c296643d..aa0d808d60 100644 --- a/packages/worker/src/api/routes/global/tests/templates.spec.ts +++ b/packages/worker/src/api/routes/global/tests/templates.spec.ts @@ -1,4 +1,17 @@ +import { + addBaseTemplates, + EmailTemplates, + getTemplates, +} from "../../../../constants/templates" +import { + EmailTemplatePurpose, + TemplateMetadata, + TemplateMetadataNames, + TemplateType, +} from "../../../../constants" import { TestConfiguration } from "../../../../tests" +import { join } from "path" +import { readStaticFile } from "../../../../../src/utilities/fileSystem" // TODO @@ -18,18 +31,85 @@ describe("/api/global/template", () => { }) describe("GET /api/global/template/definitions", () => { - it("retrieves definitions", () => {}) + describe("retrieves definitions", () => { + it("checks description definitions", async () => { + let result = await config.api.templates.definitions() + + expect(result.body.info[EmailTemplatePurpose.BASE].description).toEqual( + TemplateMetadata[TemplateType.EMAIL][0].description + ) + expect( + result.body.info[EmailTemplatePurpose.PASSWORD_RECOVERY].description + ).toEqual(TemplateMetadata[TemplateType.EMAIL][1].description) + expect( + result.body.info[EmailTemplatePurpose.WELCOME].description + ).toEqual(TemplateMetadata[TemplateType.EMAIL][2].description) + expect( + result.body.info[EmailTemplatePurpose.INVITATION].description + ).toEqual(TemplateMetadata[TemplateType.EMAIL][3].description) + expect( + result.body.info[EmailTemplatePurpose.CUSTOM].description + ).toEqual(TemplateMetadata[TemplateType.EMAIL][4].description) + }) + + it("checks description bindings", async () => { + let result = await config.api.templates.definitions() + + expect(result.body.bindings[EmailTemplatePurpose.BASE]).toEqual( + TemplateMetadata[TemplateType.EMAIL][0].bindings + ) + expect( + result.body.bindings[EmailTemplatePurpose.PASSWORD_RECOVERY] + ).toEqual(TemplateMetadata[TemplateType.EMAIL][1].bindings) + expect(result.body.bindings[EmailTemplatePurpose.WELCOME]).toEqual( + TemplateMetadata[TemplateType.EMAIL][2].bindings + ) + expect(result.body.bindings[EmailTemplatePurpose.INVITATION]).toEqual( + TemplateMetadata[TemplateType.EMAIL][3].bindings + ) + expect(result.body.bindings[EmailTemplatePurpose.CUSTOM]).toEqual( + TemplateMetadata[TemplateType.EMAIL][4].bindings + ) + }) + }) }) - describe("POST /api/global/template", () => {}) + describe("POST /api/global/template", () => { + it("adds a new template", async () => { + let purpose = "base" + let contents = "Test contents" + let updatedTemplate = { + contents: contents, + purpose: purpose, + type: "email", + } + await config.api.templates.saveTemplate(updatedTemplate) + let res = await config.api.templates.getTemplate() + let newTemplate = res.body.find((t: any) => (t.purpose = purpose)) + expect(newTemplate.contents).toEqual(contents) + }) + }) - describe("GET /api/global/template", () => {}) - - describe("GET /api/global/template/:type", () => {}) - - describe("GET /api/global/template/:ownerId", () => {}) - - describe("GET /api/global/template/:id", () => {}) - - describe("DELETE /api/global/template/:id/:rev", () => {}) + describe("GET /api/global/template", () => { + it("fetches templates", async () => { + let res = await config.api.templates.getTemplate() + expect( + res.body.find((t: any) => t.purpose === EmailTemplatePurpose.BASE) + ).toBeDefined() + expect( + res.body.find((t: any) => t.purpose === EmailTemplatePurpose.CUSTOM) + ).toBeDefined() + expect( + res.body.find((t: any) => t.purpose === EmailTemplatePurpose.INVITATION) + ).toBeDefined() + expect( + res.body.find( + (t: any) => t.purpose === EmailTemplatePurpose.PASSWORD_RECOVERY + ) + ).toBeDefined() + expect( + res.body.find((t: any) => t.purpose === EmailTemplatePurpose.WELCOME) + ).toBeDefined() + }) + }) }) diff --git a/packages/worker/src/api/routes/global/tests/users.spec.ts b/packages/worker/src/api/routes/global/tests/users.spec.ts index 3b732cb3d9..10c29809b9 100644 --- a/packages/worker/src/api/routes/global/tests/users.spec.ts +++ b/packages/worker/src/api/routes/global/tests/users.spec.ts @@ -116,7 +116,7 @@ describe("/api/global/users", () => { it("should ignore users existing in other tenants", async () => { const user = await config.createUser() - jest.resetAllMocks() + jest.clearAllMocks() await tenancy.doInTenant(TENANT_1, async () => { const response = await config.api.users.bulkCreateUsers([user]) @@ -229,7 +229,7 @@ describe("/api/global/users", () => { it("should not be able to create user that exists in other tenant", async () => { const user = await config.createUser() - jest.resetAllMocks() + jest.clearAllMocks() await tenancy.doInTenant(TENANT_1, async () => { delete user._id diff --git a/packages/worker/src/constants/index.ts b/packages/worker/src/constants/index.ts index 4ed2c99714..dba058eabb 100644 --- a/packages/worker/src/constants/index.ts +++ b/packages/worker/src/constants/index.ts @@ -27,6 +27,14 @@ export enum EmailTemplatePurpose { CUSTOM = "custom", } +export enum TemplateMetadataNames { + BASE = "Base format", + PASSWORD_RECOVERY = "Password recovery", + WELCOME = "User welcome", + INVITATION = "User invitation", + CUSTOM = "Custom", +} + export enum InternalTemplateBinding { PLATFORM_URL = "platformUrl", COMPANY = "company", @@ -93,7 +101,7 @@ export const TemplateBindings = { export const TemplateMetadata = { [TemplateType.EMAIL]: [ { - name: "Base format", + name: TemplateMetadataNames.BASE, description: "This is the base template, all others are based on it. The {{ body }} will be replaced with another email template.", category: "miscellaneous", @@ -110,7 +118,7 @@ export const TemplateMetadata = { ], }, { - name: "Password recovery", + name: TemplateMetadataNames.PASSWORD_RECOVERY, description: "When a user requests a password reset they will receive an email built with this template.", category: "user management", @@ -129,7 +137,7 @@ export const TemplateMetadata = { ], }, { - name: "User welcome", + name: TemplateMetadataNames.WELCOME, description: "When a new user is added they will be sent a welcome email using this template.", category: "user management", @@ -137,7 +145,7 @@ export const TemplateMetadata = { bindings: [], }, { - name: "User invitation", + name: TemplateMetadataNames.INVITATION, description: "When inviting a user via the email on-boarding this template will be used.", category: "user management", @@ -156,7 +164,7 @@ export const TemplateMetadata = { ], }, { - name: "Custom", + name: TemplateMetadataNames.CUSTOM, description: "A custom template, this is currently used for SMTP email actions in automations.", category: "automations", diff --git a/packages/worker/src/environment.ts b/packages/worker/src/environment.ts index 37d40a2a9c..c9c82b4e20 100644 --- a/packages/worker/src/environment.ts +++ b/packages/worker/src/environment.ts @@ -91,6 +91,11 @@ for (let [key, value] of Object.entries(environment)) { // @ts-ignore environment[key] = 0 } + // handle the edge case of "false" to disable an environment variable + if (value === "false") { + // @ts-ignore + environment[key] = 0 + } } export = environment diff --git a/packages/worker/src/index.ts b/packages/worker/src/index.ts index 34e6937f3a..e88f4952f4 100644 --- a/packages/worker/src/index.ts +++ b/packages/worker/src/index.ts @@ -24,7 +24,7 @@ import * as redis from "./utilities/redis" const Sentry = require("@sentry/node") const koaSession = require("koa-session") const logger = require("koa-pino-logger") -const destroyable = require("server-destroy") +import destroyable from "server-destroy" // this will setup http and https proxies form env variables bootstrap() @@ -79,7 +79,6 @@ server.on("close", async () => { const shutdown = () => { server.close() - // @ts-ignore server.destroy() } diff --git a/packages/worker/src/sdk/users/users.ts b/packages/worker/src/sdk/users/users.ts index aedbd59703..f3117b63ab 100644 --- a/packages/worker/src/sdk/users/users.ts +++ b/packages/worker/src/sdk/users/users.ts @@ -25,6 +25,7 @@ import { InviteUsersRequest, InviteUsersResponse, MigrationType, + PlatformUser, PlatformUserByEmail, RowResponse, SearchUsersRequest, @@ -153,10 +154,26 @@ const buildUser = async ( return fullUser } +// lookup, could be email or userId, either will return a doc +export const getPlatformUser = 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 = dbUtils.queryPlatformView( + ViewName.PLATFORM_USERS_LOWERCASE, + { + keys: [identifier.toLowerCase()], + include_docs: true, + } + ) as Promise + return response +} + const validateUniqueUser = async (email: string, tenantId: string) => { // check budibase users in other tenants if (env.MULTI_TENANCY) { - const tenantUser = await tenancy.getTenantUser(email) + const tenantUser = await getPlatformUser(email) if (tenantUser != null && tenantUser.tenantId !== tenantId) { throw `Unavailable` } diff --git a/packages/worker/src/tests/TestConfiguration.ts b/packages/worker/src/tests/TestConfiguration.ts index 871b7ec6a6..21b59ba8ed 100644 --- a/packages/worker/src/tests/TestConfiguration.ts +++ b/packages/worker/src/tests/TestConfiguration.ts @@ -1,4 +1,12 @@ -import "./mocks" +import mocks from "./mocks" + +// init the licensing mock +import * as pro from "@budibase/pro" +mocks.licenses.init(pro) + +// use unlimited license by default +mocks.licenses.useUnlimited() + import * as dbConfig from "../db" dbConfig.init() import env from "../environment" diff --git a/packages/worker/src/tests/api/groups.ts b/packages/worker/src/tests/api/groups.ts new file mode 100644 index 0000000000..4522790d32 --- /dev/null +++ b/packages/worker/src/tests/api/groups.ts @@ -0,0 +1,26 @@ +import { UserGroup } from "@budibase/types" +import TestConfiguration from "../TestConfiguration" +import { TestAPI } from "./base" + +export class GroupsAPI extends TestAPI { + constructor(config: TestConfiguration) { + super(config) + } + + saveGroup = (group: UserGroup) => { + return this.request + .post(`/api/global/groups`) + .send(group) + .set(this.config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + } + + deleteGroup = (id: string, rev: string) => { + return this.request + .delete(`/api/global/groups/${id}/${rev}`) + .set(this.config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + } +} diff --git a/packages/worker/src/tests/api/index.ts b/packages/worker/src/tests/api/index.ts index bc0271b9c6..0bd0308e2f 100644 --- a/packages/worker/src/tests/api/index.ts +++ b/packages/worker/src/tests/api/index.ts @@ -10,7 +10,10 @@ import { MigrationAPI } from "./migrations" import { StatusAPI } from "./status" import { RestoreAPI } from "./restore" import { TenantAPI } from "./tenants" - +import { GroupsAPI } from "./groups" +import { RolesAPI } from "./roles" +import { TemplatesAPI } from "./templates" +import { LicenseAPI } from "./license" export default class API { accounts: AccountAPI auth: AuthAPI @@ -23,6 +26,10 @@ export default class API { status: StatusAPI restore: RestoreAPI tenants: TenantAPI + groups: GroupsAPI + roles: RolesAPI + templates: TemplatesAPI + license: LicenseAPI constructor(config: TestConfiguration) { this.accounts = new AccountAPI(config) @@ -36,5 +43,9 @@ export default class API { this.status = new StatusAPI(config) this.restore = new RestoreAPI(config) this.tenants = new TenantAPI(config) + this.groups = new GroupsAPI(config) + this.roles = new RolesAPI(config) + this.templates = new TemplatesAPI(config) + this.license = new LicenseAPI(config) } } diff --git a/packages/worker/src/tests/api/license.ts b/packages/worker/src/tests/api/license.ts new file mode 100644 index 0000000000..9d7745a80e --- /dev/null +++ b/packages/worker/src/tests/api/license.ts @@ -0,0 +1,17 @@ +import TestConfiguration from "../TestConfiguration" +import { TestAPI } from "./base" + +export class LicenseAPI extends TestAPI { + constructor(config: TestConfiguration) { + super(config) + } + + activate = async (licenseKey: string) => { + return this.request + .post("/api/global/license/activate") + .send({ licenseKey: licenseKey }) + .set(this.config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + } +} diff --git a/packages/worker/src/tests/api/roles.ts b/packages/worker/src/tests/api/roles.ts new file mode 100644 index 0000000000..8e7647583a --- /dev/null +++ b/packages/worker/src/tests/api/roles.ts @@ -0,0 +1,32 @@ +import TestConfiguration from "../TestConfiguration" +import { TestAPI, TestAPIOpts } from "./base" + +export class RolesAPI extends TestAPI { + constructor(config: TestConfiguration) { + super(config) + } + + get = (opts?: TestAPIOpts) => { + return this.request + .get(`/api/global/roles`) + .set(this.config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(opts?.status ? opts.status : 200) + } + + find = (appId: string, opts?: TestAPIOpts) => { + return this.request + .get(`/api/global/roles/${appId}`) + .set(this.config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(opts?.status ? opts.status : 200) + } + + remove = (appId: string, opts?: TestAPIOpts) => { + return this.request + .delete(`/api/global/roles/${appId}`) + .set(this.config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(opts?.status ? opts.status : 200) + } +} diff --git a/packages/worker/src/tests/api/templates.ts b/packages/worker/src/tests/api/templates.ts new file mode 100644 index 0000000000..0c8110394f --- /dev/null +++ b/packages/worker/src/tests/api/templates.ts @@ -0,0 +1,30 @@ +import TestConfiguration from "../TestConfiguration" +import { TestAPI, TestAPIOpts } from "./base" + +export class TemplatesAPI extends TestAPI { + constructor(config: TestConfiguration) { + super(config) + } + + definitions = (opts?: TestAPIOpts) => { + return this.request + .get(`/api/global/template/definitions`) + .set(opts?.headers ? opts.headers : this.config.defaultHeaders()) + .expect(opts?.status ? opts.status : 200) + } + + getTemplate = (opts?: TestAPIOpts) => { + return this.request + .get(`/api/global/template`) + .set(opts?.headers ? opts.headers : this.config.defaultHeaders()) + .expect(opts?.status ? opts.status : 200) + } + + saveTemplate = (data: any, opts?: TestAPIOpts) => { + return this.request + .post(`/api/global/template`) + .send(data) + .set(opts?.headers ? opts.headers : this.config.defaultHeaders()) + .expect(opts?.status ? opts.status : 200) + } +} diff --git a/packages/worker/src/tests/jestSetup.ts b/packages/worker/src/tests/jestSetup.ts index fee704ae45..ab82cb5be7 100644 --- a/packages/worker/src/tests/jestSetup.ts +++ b/packages/worker/src/tests/jestSetup.ts @@ -22,7 +22,9 @@ mocks.fetch.enable() 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/worker/src/tests/mocks/email.js b/packages/worker/src/tests/mocks/email.ts similarity index 90% rename from packages/worker/src/tests/mocks/email.js rename to packages/worker/src/tests/mocks/email.ts index 5e34c38f41..c16c58ddb5 100644 --- a/packages/worker/src/tests/mocks/email.js +++ b/packages/worker/src/tests/mocks/email.ts @@ -1,4 +1,4 @@ -exports.mock = () => { +export function mock() { // mock the email system const sendMailMock = jest.fn() const nodemailer = require("nodemailer") diff --git a/packages/worker/src/tests/mocks/index.ts b/packages/worker/src/tests/mocks/index.ts index e4b68bbfd4..4d69e04716 100644 --- a/packages/worker/src/tests/mocks/index.ts +++ b/packages/worker/src/tests/mocks/index.ts @@ -1,7 +1,7 @@ -const email = require("./email") -import { mocks as coreMocks } from "@budibase/backend-core/tests" +import * as email from "./email" +import { mocks } from "@budibase/backend-core/tests" export = { email, - ...coreMocks, + ...mocks, } diff --git a/packages/worker/src/tests/structures/configs.ts b/packages/worker/src/tests/structures/configs.ts index a692e77987..2c76f271c4 100644 --- a/packages/worker/src/tests/structures/configs.ts +++ b/packages/worker/src/tests/structures/configs.ts @@ -1,5 +1,5 @@ -const { Config } = require("../../constants") -const { utils } = require("@budibase/backend-core") +import { Config } from "../../constants" +import { utils } from "@budibase/backend-core" export function oidc(conf?: any) { return { diff --git a/packages/worker/src/tests/structures/groups.ts b/packages/worker/src/tests/structures/groups.ts index 874d1b6a10..0f7e518895 100644 --- a/packages/worker/src/tests/structures/groups.ts +++ b/packages/worker/src/tests/structures/groups.ts @@ -4,7 +4,7 @@ export const UserGroup = () => { color: "var(--spectrum-global-color-blue-600)", icon: "UserGroup", name: "New group", - roles: {}, + roles: { app_uuid1: "ADMIN", app_uuid2: "POWER" }, users: [], } return group diff --git a/packages/worker/src/tests/structures/users.ts b/packages/worker/src/tests/structures/users.ts index bef9f38586..3348670b7d 100644 --- a/packages/worker/src/tests/structures/users.ts +++ b/packages/worker/src/tests/structures/users.ts @@ -10,7 +10,7 @@ export const user = (userProps?: any): User => { return { email: newEmail(), password: "test", - roles: {}, + roles: { app_test: "admin" }, ...userProps, } } diff --git a/packages/worker/tsconfig.json b/packages/worker/tsconfig.json index 62d167075c..3f6d4b30dc 100644 --- a/packages/worker/tsconfig.json +++ b/packages/worker/tsconfig.json @@ -25,7 +25,6 @@ "package.json" ], "exclude": [ - "node_modules", "dist" ] } \ No newline at end of file diff --git a/packages/worker/yarn.lock b/packages/worker/yarn.lock index 2655d00175..277b9b846e 100644 --- a/packages/worker/yarn.lock +++ b/packages/worker/yarn.lock @@ -470,14 +470,16 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@budibase/backend-core@2.1.43-alpha.15": - version "2.1.43-alpha.15" - resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.1.43-alpha.15.tgz#0c1d3ca30010fac5c4828ddac0bda38c0ed2e473" - integrity sha512-zUj/+q4mOGXTmElpJ3B9hBngKnw+qDGLp5wEMqQGSjmcBq/SOCJBpM1yHSqDbMWBJguSmshPMrEjGMe78r+BOQ== +"@budibase/backend-core@2.2.12-alpha.4": + version "2.2.12-alpha.4" + resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.2.12-alpha.4.tgz#02fec92bd744acda0a5e205c8b898eaa638a797f" + integrity sha512-62+eaj64wTFEC3XnHBan9sqW5XxQ3NAKyeu6n7H7ABFuk+a5g8Ym4VWbU7xgsyqVQOO4XzWzDjE0kF9e4ntGtg== dependencies: - "@budibase/types" "2.1.43-alpha.15" + "@budibase/nano" "10.1.1" + "@budibase/types" "2.2.12-alpha.4" "@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" @@ -486,11 +488,10 @@ 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" - nano "^10.1.0" node-fetch "2.6.7" passport-google-oauth "2.0.0" passport-jwt "4.0.0" @@ -507,22 +508,35 @@ uuid "8.3.2" zlib "1.0.5" -"@budibase/pro@2.1.43-alpha.15": - version "2.1.43-alpha.15" - resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.1.43-alpha.15.tgz#2ad444af59a0f28794c0dcf3f1112e74b5b1eb1c" - integrity sha512-s7PNnWe9Mx5poF9+hgnTgY4B1zZqLlmJT9o7IY99uDf1sYCf6CRX4uJ+WG9+ROL/UcKPXtllZQuxompreyA8ow== +"@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: - "@budibase/backend-core" "2.1.43-alpha.15" - "@budibase/types" "2.1.43-alpha.15" + "@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" + +"@budibase/pro@2.2.12-alpha.4": + version "2.2.12-alpha.4" + resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.2.12-alpha.4.tgz#026899d3f333c8fd1788d80185d96aa3daba5d03" + integrity sha512-f/5dkyS9c8FEY6BoB6pjiDn7f1sg0xlkOEou6TPg/HhFZxeHi11OJHNdso46Eu0tyb4qylpk0TRFKzkhWM0cxQ== + dependencies: + "@budibase/backend-core" "2.2.12-alpha.4" + "@budibase/types" "2.2.12-alpha.4" "@koa/router" "8.0.8" bull "4.10.1" joi "17.6.0" + jsonwebtoken "8.5.1" node-fetch "^2.6.1" -"@budibase/types@2.1.43-alpha.15": - version "2.1.43-alpha.15" - resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.1.43-alpha.15.tgz#0d5b372d615f1f9509a606fb952f4eeabeda19c7" - integrity sha512-ATh/UyYGZ567UDcQo9/tuDa6SXm7a5WcXBMsIZVLlGSg56dECpiz7SIerr+sOtjtAgdHqIy4aYcQmgA5k9eWdg== +"@budibase/types@2.2.12-alpha.4": + version "2.2.12-alpha.4" + resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.2.12-alpha.4.tgz#4acc580dcc15bc400f193c175a750f249a7ae621" + integrity sha512-d77CNXwMUzOHiqmTBZxxQZkbQLehU9564UCIFdqkofFWkwHpaVtmmnGuuSfnvD5E3XwV43DEtVJWLMv9oaMw3Q== "@cspotcode/source-map-support@^0.8.0": version "0.8.1" @@ -1324,10 +1338,10 @@ "@types/koa-compose" "*" "@types/node" "*" -"@types/koa__router@8.0.11": - version "8.0.11" - resolved "https://registry.yarnpkg.com/@types/koa__router/-/koa__router-8.0.11.tgz#d7b37e6db934fc072ea1baa2ab92bc8ac4564f3e" - integrity sha512-WXgKWpBsbS14kzmzD9LeFapOIa678h7zvUHxDwXwSx4ETKXhXLVUAToX6jZ/U7EihM7qwyD9W/BZvB0MRu7MTQ== +"@types/koa__router@8.0.8": + version "8.0.8" + resolved "https://registry.yarnpkg.com/@types/koa__router/-/koa__router-8.0.8.tgz#b1e0e9a512498777d3366bbdf0e853df27ec831c" + integrity sha512-9pGCaDtzCsj4HJ8HmGuqzk8+s57sPj4njWd08GG5o92n5Xp9io2snc40CPpXFhoKcZ8OKhuu6ht4gNou9e1C2w== dependencies: "@types/koa" "*" @@ -1535,6 +1549,13 @@ "@types/mime" "^1" "@types/node" "*" +"@types/server-destroy@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/server-destroy/-/server-destroy-1.0.1.tgz#6010a89e2df4f2c15a265fe73c70fd3641486530" + integrity sha512-77QGr7waZbE0Y0uF+G+uH3H3SmhyA78Jf2r5r7QSrpg0U3kSXduWpGjzP9PvPLR/KCy+kHjjpnugRHsYTnHopg== + dependencies: + "@types/node" "*" + "@types/stack-utils@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" @@ -1875,6 +1896,13 @@ atomic-sleep@^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" @@ -4842,6 +4870,16 @@ jsonwebtoken@8.5.1, jsonwebtoken@^8.2.0: ms "^2.1.1" semver "^5.6.0" +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" + jsprim@^1.2.2: version "1.4.2" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" @@ -5218,6 +5256,11 @@ lodash@4.17.21, lodash@^4.17.14, lodash@^4.17.19, lodash@^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" @@ -5493,18 +5536,6 @@ mute-stream@0.0.8: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== -nano@^10.1.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/nano/-/nano-10.1.0.tgz#afdd5a7440e62f09a8e23f41fcea328d27383922" - integrity sha512-COeN2TpLcHuSN44QLnPmfZCoCsKAg8/aelPOVqqm/2/MvRHDEA11/Kld5C4sLzDlWlhFZ3SO2WGJGevCsvcEzQ== - 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" - napi-macros@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/napi-macros/-/napi-macros-2.0.0.tgz#2b6bae421e7b96eb687aa6c77a7858640670001b" @@ -6877,6 +6908,13 @@ semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +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" + serialize-error@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18" diff --git a/pull_request_template.md b/pull_request_template.md index ade39d55e4..068776c4ab 100644 --- a/pull_request_template.md +++ b/pull_request_template.md @@ -1,3 +1,6 @@ +## Checklist +- [ ] I have searched budibase discussions and github issues to check if my issue already exists + ## Description _Describe the problem or feature in addition to a link to the relevant github issues._ diff --git a/qa-core/src/config/internal-api/TestConfiguration/applications.ts b/qa-core/src/config/internal-api/TestConfiguration/applications.ts index 13d0969854..1cfd025974 100644 --- a/qa-core/src/config/internal-api/TestConfiguration/applications.ts +++ b/qa-core/src/config/internal-api/TestConfiguration/applications.ts @@ -13,12 +13,13 @@ export default class AppApi { constructor(apiClient: InternalAPIClient) { this.api = apiClient } - + // TODO Fix the fetch apps to receive an optional number of apps and compare if the received app is more or less. + // each possible scenario should have its own method. async fetchEmptyAppList(): Promise<[Response, Application[]]> { const response = await this.api.get(`/applications?status=all`) const json = await response.json() expect(response).toHaveStatusCode(200) - expect(json.length).toEqual(0) + expect(json.length).toBeGreaterThanOrEqual(0) return [response, json] } @@ -32,9 +33,9 @@ export default class AppApi { async canRender(): Promise<[Response, boolean]> { const response = await this.api.get("/routing/client") + expect(response).toHaveStatusCode(200) const json = await response.json() const publishedAppRenders = Object.keys(json.routes).length > 0 - expect(response).toHaveStatusCode(200) expect(publishedAppRenders).toBe(true) return [response, publishedAppRenders] } @@ -47,15 +48,10 @@ export default class AppApi { return [response, json] } - async publish(appUrl: string): Promise<[Response, DeployConfig]> { - const response = await this.api.post("/deploy") + async publish(appId: string | undefined): Promise<[Response, DeployConfig]> { + const response = await this.api.post(`/applications/${appId}/publish`) const json = await response.json() expect(response).toHaveStatusCode(200) - expect(json).toEqual({ - _id: expect.any(String), - appUrl: appUrl, - status: "SUCCESS", - }) return [response, json] } @@ -152,13 +148,9 @@ export default class AppApi { return [response, json] } - async unpublish(appId: string): Promise<[Response, UnpublishAppResponse]> { - const response = await this.api.del(`/applications/${appId}?unpublish=1`) - expect(response).toHaveStatusCode(200) - const json = await response.json() - expect(json.data.ok).toBe(true) - expect(json.ok).toBe(true) - expect(json.status).toBe(200) - return [response, json] + async unpublish(appId: string): Promise<[Response]> { + const response = await this.api.post(`/applications/${appId}/unpublish`) + expect(response).toHaveStatusCode(204) + return [response] } } diff --git a/qa-core/src/config/internal-api/TestConfiguration/auth.ts b/qa-core/src/config/internal-api/TestConfiguration/auth.ts index d83c859ab3..3fe57c50be 100644 --- a/qa-core/src/config/internal-api/TestConfiguration/auth.ts +++ b/qa-core/src/config/internal-api/TestConfiguration/auth.ts @@ -8,7 +8,7 @@ export default class AuthApi { this.api = apiClient } - async login(): Promise<[Response, any]> { + async loginAsAdmin(): Promise<[Response, any]> { const response = await this.api.post(`/global/auth/default/login`, { body: { username: process.env.BB_ADMIN_USER_EMAIL, @@ -20,6 +20,19 @@ export default class AuthApi { return [response, cookie] } + async login(email: String, password: String): Promise<[Response, any]> { + const response = await this.api.post(`/global/auth/default/login`, { + body: { + username: email, + password: password, + }, + }) + expect(response).toHaveStatusCode(200) + const cookie = response.headers.get("set-cookie") + this.api.cookie = cookie as any + return [response, cookie] + } + async logout(): Promise { return this.api.post(`/global/auth/logout`) } diff --git a/qa-core/src/config/internal-api/TestConfiguration/index.ts b/qa-core/src/config/internal-api/TestConfiguration/index.ts index ab996f7144..08ca0d3cf3 100644 --- a/qa-core/src/config/internal-api/TestConfiguration/index.ts +++ b/qa-core/src/config/internal-api/TestConfiguration/index.ts @@ -4,6 +4,7 @@ import InternalAPIClient from "./InternalAPIClient" import TablesApi from "./tables" import RowApi from "./rows" import ScreenApi from "./screens" +import UserManagementApi from "./userManagement" export default class TestConfiguration { applications: ApplicationApi @@ -12,6 +13,7 @@ export default class TestConfiguration { context: T tables: TablesApi rows: RowApi + users: UserManagementApi constructor(apiClient: InternalAPIClient) { this.applications = new ApplicationApi(apiClient) @@ -19,11 +21,19 @@ export default class TestConfiguration { this.rows = new RowApi(apiClient) this.auth = new AuthApi(apiClient) this.screen = new ScreenApi(apiClient) + this.users = new UserManagementApi(apiClient) this.context = {} } - async beforeAll() { - await this.auth.login() + async loginAsAdmin() { + await this.auth.login( + process.env.BB_ADMIN_USER_EMAIL, + process.env.BB_ADMIN_USER_PASSWORD + ) + } + + async login(email: string, password: string) { + await this.auth.login(email, password) } async afterAll() { diff --git a/qa-core/src/config/internal-api/TestConfiguration/tables.ts b/qa-core/src/config/internal-api/TestConfiguration/tables.ts index 5b7e1648a0..5e52b71c94 100644 --- a/qa-core/src/config/internal-api/TestConfiguration/tables.ts +++ b/qa-core/src/config/internal-api/TestConfiguration/tables.ts @@ -39,6 +39,14 @@ export default class TablesApi { return [response, json] } + async forbiddenSave(body: any): Promise<[Response, Table]> { + const response = await this.api.post(`/tables`, { body }) + const json = await response.json() + expect(response).toHaveStatusCode(403) + + return [response, json] + } + async delete( id: string, revId: string diff --git a/qa-core/src/config/internal-api/TestConfiguration/userManagement.ts b/qa-core/src/config/internal-api/TestConfiguration/userManagement.ts new file mode 100644 index 0000000000..20909e8e86 --- /dev/null +++ b/qa-core/src/config/internal-api/TestConfiguration/userManagement.ts @@ -0,0 +1,131 @@ +import { Response } from "node-fetch" +import { Role, User, UserDeletedEvent, UserRoles } from "@budibase/types" +import InternalAPIClient from "./InternalAPIClient" +import { responseMessage } from "../fixtures/types/responseMessage" + +export default class UserManagementApi { + api: InternalAPIClient + + constructor(apiClient: InternalAPIClient) { + this.api = apiClient + } + + async search(): Promise<[Response, Partial[]]> { + const response = await this.api.post(`/global/users/search`, {}) + const json = await response.json() + expect(response).toHaveStatusCode(200) + expect(json.data.length).toBeGreaterThan(0) + return [response, json] + } + + async getSelf(): Promise<[Response, Partial]> { + const response = await this.api.get(`/global/self`) + const json = await response.json() + expect(response).toHaveStatusCode(200) + return [response, json] + } + + async getAll(): Promise<[Response, Partial[]]> { + const response = await this.api.get(`/global/users`) + const json = await response.json() + expect(response).toHaveStatusCode(200) + expect(json.length).toBeGreaterThan(0) + return [response, json] + } + + // This endpoint is used for one or more users when we want add users with passwords set. + async addMultiple(userList: Partial[]): Promise<[Response, any]> { + const body = { + create: { + users: userList, + groups: [], + }, + } + const response = await this.api.post(`/global/users/bulk`, { body }) + const json = await response.json() + expect(response).toHaveStatusCode(200) + expect(json.created.unsuccessful.length).toEqual(0) + expect(json.created.successful.length).toEqual(body.create.users.length) + return [response, json] + } + + async deleteMultiple(userId: string[]): Promise<[Response, responseMessage]> { + const body = { + delete: { + userIds: [userId], + }, + } + const response = await this.api.post(`/global/users/bulk`, { body }) + const json = await response.json() + expect(response).toHaveStatusCode(200) + expect(json.deleted.successful.length).toEqual(1) + expect(json.deleted.unsuccessful.length).toEqual(0) + expect(json.deleted.successful[0].userId).toEqual(userId) + return [response, json] + } + async delete(userId: string): Promise<[Response, UserDeletedEvent]> { + const response = await this.api.del(`/global/users/${userId}`) + const json = await response.json() + expect(response).toHaveStatusCode(200) + expect(json.message).toEqual(`User ${userId} deleted.`) + return [response, json] + } + + async invite(body: any): Promise<[Response, responseMessage]> { + const response = await this.api.post(`/global/users/multi/invite`, { body }) + const json = await response.json() + expect(response).toHaveStatusCode(200) + expect(json.unsuccessful.length).toEqual(0) + expect(json.successful.length).toEqual(body.length) + + return [response, json] + } + + async getRoles(): Promise<[Response, Role[]]> { + const response = await this.api.get(`/roles`) + const json = await response.json() + expect(response).toHaveStatusCode(200) + return [response, json] + } + + async updateInfo(body: any): Promise<[Response, User]> { + const response = await this.api.post(`/global/users/`, { body }) + const json = await response.json() + expect(response).toHaveStatusCode(200) + expect(json._id).toEqual(body._id) + expect(json._rev).not.toEqual(body._rev) + return [response, json] + } + + async forcePasswordReset(body: any): Promise<[Response, User]> { + const response = await this.api.post(`/global/users/`, { body }) + const json = await response.json() + expect(response).toHaveStatusCode(200) + expect(json._id).toEqual(body._id) + expect(json._rev).not.toEqual(body._rev) + return [response, json] + } + + async getInfo(userId: string): Promise<[Response, User]> { + const response = await this.api.get(`/global/users/${userId}`) + const json = await response.json() + expect(response).toHaveStatusCode(200) + return [response, json] + } + + async changeSelfPassword(body: Partial): Promise<[Response, User]> { + const response = await this.api.post(`/global/self`, { body }) + const json = await response.json() + expect(response).toHaveStatusCode(200) + expect(json._id).toEqual(body._id) + expect(json._rev).not.toEqual(body._rev) + return [response, json] + } + + async createRole(body: Partial): Promise<[Response, UserRoles]> { + const response = await this.api.post(`/roles`, { body }) + const json = await response.json() + expect(response).toHaveStatusCode(200) + return [response, json] + } +} diff --git a/qa-core/src/config/internal-api/fixtures/applications.ts b/qa-core/src/config/internal-api/fixtures/applications.ts index 200aa9abff..45d82cf4f0 100644 --- a/qa-core/src/config/internal-api/fixtures/applications.ts +++ b/qa-core/src/config/internal-api/fixtures/applications.ts @@ -1,7 +1,9 @@ import generator from "../../generator" import { Application } from "@budibase/server/api/controllers/public/mapping/types" +import { Template } from "@budibase/types" +import { App } from "@budibase/types" -const generate = ( +export const generateApp = ( overrides: Partial = {} ): Partial => ({ name: generator.word() + generator.hash(), @@ -9,4 +11,14 @@ const generate = ( ...overrides, }) -export default generate +// Applications type doesn't work here, save to add useTemplate parameter? +export const appFromTemplate = (): any => { + return { + name: generator.word(), + url: `/${generator.word()}`, + useTemplate: "true", + templateName: "Near Miss Register", + templateKey: "app/near-miss-register", + templateFile: undefined, + } +} diff --git a/qa-core/src/config/internal-api/fixtures/userManagement.ts b/qa-core/src/config/internal-api/fixtures/userManagement.ts new file mode 100644 index 0000000000..90ea6bd738 --- /dev/null +++ b/qa-core/src/config/internal-api/fixtures/userManagement.ts @@ -0,0 +1,82 @@ +import generator from "../../generator" +import { User } from "@budibase/types" + +const generateDeveloper = (): Partial => { + const randomId = generator.guid() + return { + email: `pedro+${randomId}@budibase.com`, + password: randomId, + roles: {}, + forceResetPassword: true, + builder: { + global: true, + }, + } +} + +const generateAdmin = (): Partial => { + const randomId = generator.guid() + return { + email: `pedro+${randomId}@budibase.com`, + password: randomId, + roles: {}, + forceResetPassword: true, + admin: { + global: true, + }, + builder: { + global: true, + }, + } +} +const generateAppUser = (): Partial => { + const randomId = generator.guid() + return { + email: `pedro+${randomId}@budibase.com`, + password: randomId, + roles: {}, + forceResetPassword: true, + admin: { + global: false, + }, + builder: { + global: false, + }, + } +} + +export const generateInviteUser = (): Object[] => { + const randomId = generator.guid() + return [ + { + email: `pedro+${randomId}@budibase.com`, + userInfo: { + userGroups: [], + }, + }, + ] +} + +export const generateUser = ( + amount: number = 1, + role?: string +): Partial[] => { + const userList: Partial[] = [] + for (let i = 0; i < amount; i++) { + switch (role) { + case "admin": + userList.push(generateAdmin()) + break + case "developer": + userList.push(generateDeveloper()) + break + case "appUser": + userList.push(generateAppUser()) + break + default: + userList.push(generateAppUser()) + break + } + } + return userList +} diff --git a/qa-core/src/config/public-api/TestConfiguration/applications.ts b/qa-core/src/config/public-api/TestConfiguration/applications.ts index 79de8f570d..ab25930544 100644 --- a/qa-core/src/config/public-api/TestConfiguration/applications.ts +++ b/qa-core/src/config/public-api/TestConfiguration/applications.ts @@ -46,4 +46,21 @@ export default class AppApi { const json = await response.json() return [response, json.data] } + + async delete(id: string): Promise<[Response, Application]> { + const response = await this.api.del(`/applications/${id}`) + const json = await response.json() + return [response, json.data] + } + + async publish(id: string): Promise<[Response, any]> { + const response = await this.api.post(`/applications/${id}/publish`) + const json = await response.json() + return [response, json.data] + } + + async unpublish(id: string): Promise<[Response]> { + const response = await this.api.post(`/applications/${id}/unpublish`) + return [response] + } } diff --git a/qa-core/src/tests/internal-api/applications/applications.spec.ts b/qa-core/src/tests/internal-api/applications/applications.spec.ts index 98895a6f28..70724232bb 100644 --- a/qa-core/src/tests/internal-api/applications/applications.spec.ts +++ b/qa-core/src/tests/internal-api/applications/applications.spec.ts @@ -2,7 +2,10 @@ import TestConfiguration from "../../../config/internal-api/TestConfiguration" import { Application } from "@budibase/server/api/controllers/public/mapping/types" import { db } from "@budibase/backend-core" import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient" -import generateApp from "../../../config/internal-api/fixtures/applications" +import { + generateApp, + appFromTemplate, +} from "../../../config/internal-api/fixtures/applications" import generator from "../../../config/generator" import generateScreen from "../../../config/internal-api/fixtures/screens" @@ -11,23 +14,13 @@ describe("Internal API - Application creation, update, publish and delete", () = const config = new TestConfiguration(api) beforeAll(async () => { - await config.beforeAll() + await config.loginAsAdmin() }) afterAll(async () => { await config.afterAll() }) - async function createAppFromTemplate() { - return config.applications.create({ - name: generator.word(), - url: `/${generator.word()}`, - useTemplate: "true", - templateName: "Near Miss Register", - templateKey: "app/near-miss-register", - templateFile: undefined, - }) - } it("Get applications without applications", async () => { await config.applications.fetchEmptyAppList() }) @@ -59,15 +52,14 @@ describe("Internal API - Application creation, update, publish and delete", () = it("Publish app", async () => { // create the app - const appName = generator.word() - const app = await createAppFromTemplate() + const app = await config.applications.create(appFromTemplate()) config.applications.api.appId = app.appId // check preview renders await config.applications.canRender() // publish app - await config.applications.publish(app.url) + await config.applications.publish(app.appId) // check published app renders config.applications.api.appId = db.getProdAppID(app.appId!) @@ -94,7 +86,7 @@ describe("Internal API - Application creation, update, publish and delete", () = config.applications.api.appId = app.appId // publish app - await config.applications.publish(app.url) + await config.applications.publish(app._id) const [syncResponse, sync] = await config.applications.sync( app.appId @@ -126,7 +118,7 @@ describe("Internal API - Application creation, update, publish and delete", () = config.applications.api.appId = app.appId // publish app - await config.applications.publish(app.url) + await config.applications.publish(app._id) // Change/add component to the app await config.screen.create(generateScreen("BASIC")) diff --git a/qa-core/src/tests/internal-api/screens/screens.spec.ts b/qa-core/src/tests/internal-api/screens/screens.spec.ts index 218d71cb0d..06d2115af4 100644 --- a/qa-core/src/tests/internal-api/screens/screens.spec.ts +++ b/qa-core/src/tests/internal-api/screens/screens.spec.ts @@ -1,7 +1,10 @@ import TestConfiguration from "../../../config/internal-api/TestConfiguration" import { App } from "@budibase/types" import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient" -import generateApp from "../../../config/internal-api/fixtures/applications" +import { + generateApp, + appFromTemplate, +} from "../../../config/internal-api/fixtures/applications" import { Screen } from "@budibase/types" import generateScreen from "../../../config/internal-api/fixtures/screens" @@ -11,7 +14,7 @@ describe("Internal API - /screens endpoints", () => { const appConfig = new TestConfiguration(api) beforeAll(async () => { - await config.beforeAll() + await config.loginAsAdmin() }) afterAll(async () => { diff --git a/qa-core/src/tests/internal-api/tables/tables.spec.ts b/qa-core/src/tests/internal-api/tables/tables.spec.ts index 4f9e4299cf..6b2d2240e5 100644 --- a/qa-core/src/tests/internal-api/tables/tables.spec.ts +++ b/qa-core/src/tests/internal-api/tables/tables.spec.ts @@ -13,7 +13,7 @@ describe("Internal API - Application creation, update, publish and delete", () = const config = new TestConfiguration(api) beforeAll(async () => { - await config.beforeAll() + await config.loginAsAdmin() }) afterAll(async () => { diff --git a/qa-core/src/tests/internal-api/userManagement/appSpecificRoles.spec.ts b/qa-core/src/tests/internal-api/userManagement/appSpecificRoles.spec.ts new file mode 100644 index 0000000000..502ce3d767 --- /dev/null +++ b/qa-core/src/tests/internal-api/userManagement/appSpecificRoles.spec.ts @@ -0,0 +1,705 @@ +import TestConfiguration from "../../../config/internal-api/TestConfiguration" +import { Application } from "@budibase/server/api/controllers/public/mapping/types" +import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient" +import { + generateApp, + appFromTemplate, +} from "../../../config/internal-api/fixtures/applications" +import { generateUser } from "../../../config/internal-api/fixtures/userManagement" +import { User } from "@budibase/types" +import { + generateNewColumnForTable, + generateTable, +} from "../../../config/internal-api/fixtures/table" +import generateScreen from "../../../config/internal-api/fixtures/screens" +import { db } from "@budibase/backend-core" + +describe("Internal API - App Specific Roles & Permissions", () => { + const api = new InternalAPIClient() + const config = new TestConfiguration(api) + + // Before each test, login as admin. Some tests will require login as a different user + beforeEach(async () => { + await config.loginAsAdmin() + }) + + afterAll(async () => { + await config.afterAll() + }) + + it("Add BASIC user to app", async () => { + const appUser = generateUser() + expect(appUser[0].builder?.global).toEqual(false) + expect(appUser[0].admin?.global).toEqual(false) + const [createUserResponse, createUserJson] = await config.users.addMultiple( + appUser + ) + + const app = await config.applications.create(appFromTemplate()) + config.applications.api.appId = app.appId + + const [userInfoResponse, userInfoJson] = await config.users.getInfo( + createUserJson.created.successful[0]._id + ) + const body: User = { + ...userInfoJson, + roles: { + [app.appId]: "BASIC", + }, + } + await config.users.updateInfo(body) + + const [changedUserInfoResponse, changedUserInfoJson] = + await config.users.getInfo(createUserJson.created.successful[0]._id) + expect(changedUserInfoJson.roles[app.appId]).toBeDefined() + expect(changedUserInfoJson.roles[app.appId]).toEqual("BASIC") + }) + + it("Add ADMIN user to app", async () => { + const adminUser = generateUser(1, "admin") + expect(adminUser[0].builder?.global).toEqual(true) + expect(adminUser[0].admin?.global).toEqual(true) + const [createUserResponse, createUserJson] = await config.users.addMultiple( + adminUser + ) + + //const app = await config.applications.create(generateApp()) + //config.applications.api.appId = app.appId + + const app = await config.applications.create(appFromTemplate()) + config.applications.api.appId = app.appId + + const [userInfoResponse, userInfoJson] = await config.users.getInfo( + createUserJson.created.successful[0]._id + ) + const body: User = { + ...userInfoJson, + roles: { + [app.appId]: "ADMIN", + }, + } + await config.users.updateInfo(body) + + const [changedUserInfoResponse, changedUserInfoJson] = + await config.users.getInfo(createUserJson.created.successful[0]._id) + expect(changedUserInfoJson.roles[app.appId]).toBeDefined() + expect(changedUserInfoJson.roles[app.appId]).toEqual("ADMIN") + + // publish app + await config.applications.publish(app.appId) + // check published app renders + config.applications.api.appId = db.getProdAppID(app.appId!) + await config.applications.canRender() + }) + + it("Add POWER user to app", async () => { + const powerUser = generateUser(1, "developer") + expect(powerUser[0].builder?.global).toEqual(true) + + const [createUserResponse, createUserJson] = await config.users.addMultiple( + powerUser + ) + + const app = await config.applications.create(generateApp()) + config.applications.api.appId = app.appId + + const [userInfoResponse, userInfoJson] = await config.users.getInfo( + createUserJson.created.successful[0]._id + ) + const body: User = { + ...userInfoJson, + roles: { + [app.appId]: "POWER", + }, + } + await config.users.updateInfo(body) + + const [changedUserInfoResponse, changedUserInfoJson] = + await config.users.getInfo(createUserJson.created.successful[0]._id) + expect(changedUserInfoJson.roles[app.appId]).toBeDefined() + expect(changedUserInfoJson.roles[app.appId]).toEqual("POWER") + }) + + describe("Check Access for default roles", () => { + it("Check Table access for app user", async () => { + const appUser = generateUser() + expect(appUser[0].builder?.global).toEqual(false) + expect(appUser[0].admin?.global).toEqual(false) + const [createUserResponse, createUserJson] = + await config.users.addMultiple(appUser) + + const app = await config.applications.create(generateApp()) + config.applications.api.appId = app.appId + + const [userInfoResponse, userInfoJson] = await config.users.getInfo( + createUserJson.created.successful[0]._id + ) + const body: User = { + ...userInfoJson, + roles: { + [app.appId]: "BASIC", + }, + } + await config.users.updateInfo(body) + + const [changedUserInfoResponse, changedUserInfoJson] = + await config.users.getInfo(createUserJson.created.successful[0]._id) + expect(changedUserInfoJson.roles[app.appId]).toBeDefined() + expect(changedUserInfoJson.roles[app.appId]).toEqual("BASIC") + + const [createdTableResponse, createdTableData] = await config.tables.save( + generateTable() + ) + await config.login(appUser[0].email, appUser[0].password) + const newColumn = generateNewColumnForTable(createdTableData) + await config.tables.forbiddenSave(newColumn) + await config.tables.forbiddenSave(generateTable()) + }) + + it("Check Table access for developer", async () => { + const developer = generateUser(1, "developer") + expect(developer[0].builder?.global).toEqual(true) + + const [createUserResponse, createUserJson] = + await config.users.addMultiple(developer) + + const app = await config.applications.create(generateApp()) + config.applications.api.appId = app.appId + + const [userInfoResponse, userInfoJson] = await config.users.getInfo( + createUserJson.created.successful[0]._id + ) + const body: User = { + ...userInfoJson, + roles: { + [app.appId]: "POWER", + }, + } + await config.users.updateInfo(body) + + const [changedUserInfoResponse, changedUserInfoJson] = + await config.users.getInfo(createUserJson.created.successful[0]._id) + expect(changedUserInfoJson.roles[app.appId]).toBeDefined() + expect(changedUserInfoJson.roles[app.appId]).toEqual("POWER") + + const [createdTableResponse, createdTableData] = await config.tables.save( + generateTable() + ) + await config.login( + developer[0].email, + developer[0].password + ) + const newColumn = generateNewColumnForTable(createdTableData) + const [addColumnResponse, addColumnData] = await config.tables.save( + newColumn, + true + ) + }) + + it("Check Table access for admin", async () => { + const adminUser = generateUser(1, "admin") + expect(adminUser[0].builder?.global).toEqual(true) + expect(adminUser[0].admin?.global).toEqual(true) + const [createUserResponse, createUserJson] = + await config.users.addMultiple(adminUser) + + const app = await config.applications.create(generateApp()) + config.applications.api.appId = app.appId + + const [userInfoResponse, userInfoJson] = await config.users.getInfo( + createUserJson.created.successful[0]._id + ) + const body: User = { + ...userInfoJson, + roles: { + [app.appId]: "ADMIN", + }, + } + await config.users.updateInfo(body) + + const [changedUserInfoResponse, changedUserInfoJson] = + await config.users.getInfo(createUserJson.created.successful[0]._id) + expect(changedUserInfoJson.roles[app.appId]).toBeDefined() + expect(changedUserInfoJson.roles[app.appId]).toEqual("ADMIN") + + await config.login( + adminUser[0].email, + adminUser[0].password + ) + const [createdTableResponse, createdTableData] = await config.tables.save( + generateTable() + ) + const newColumn = generateNewColumnForTable(createdTableData) + const [addColumnResponse, addColumnData] = await config.tables.save( + newColumn, + true + ) + }) + }) + + describe("Screen Access for App specific roles", () => { + it("Check Screen access for BASIC Role", async () => { + // Set up user + const appUser = generateUser() + expect(appUser[0].builder?.global).toEqual(false) + expect(appUser[0].admin?.global).toEqual(false) + const [createUserResponse, createUserJson] = + await config.users.addMultiple(appUser) + + // Create App + const app = await config.applications.create(generateApp()) + config.applications.api.appId = app.appId + + // Update user roles + const [userInfoResponse, userInfoJson] = await config.users.getInfo( + createUserJson.created.successful[0]._id + ) + const prodAppId = db.getProdAppID(app.appId!) + + // Roles must always be set with prod appID + const body: User = { + ...userInfoJson, + roles: { + [prodAppId]: "BASIC", + }, + } + await config.users.updateInfo(body) + + const [changedUserInfoResponse, changedUserInfoJson] = + await config.users.getInfo(createUserJson.created.successful[0]._id) + expect(changedUserInfoJson.roles[prodAppId]).toBeDefined() + expect(changedUserInfoJson.roles[prodAppId]).toEqual("BASIC") + + await config.screen.create(generateScreen("BASIC")) + await config.screen.create(generateScreen("POWER")) + await config.screen.create(generateScreen("ADMIN")) + + await config.applications.publish(app.appId) + const [firstappPackageResponse, firstappPackageJson] = + await config.applications.getAppPackage(app.appId) + expect(firstappPackageJson.screens).toBeDefined() + expect(firstappPackageJson.screens.length).toEqual(3) + + // login with BASIC user + await config.login(appUser[0].email!, appUser[0].password!) + const [selfInfoResponse, selfInfoJson] = await config.users.getSelf() + + // fetch app package + const [appPackageResponse, appPackageJson] = + await config.applications.getAppPackage(app.appId!) + expect(appPackageJson.screens).toBeDefined() + expect(appPackageJson.screens.length).toEqual(1) + expect(appPackageJson.screens[0].routing.roleId).toEqual("BASIC") + }) + + it("Check Screen access for POWER role", async () => { + // Set up user + const appUser = generateUser() + expect(appUser[0].builder?.global).toEqual(false) + expect(appUser[0].admin?.global).toEqual(false) + const [createUserResponse, createUserJson] = + await config.users.addMultiple(appUser) + + // Create App + const app = await config.applications.create(generateApp()) + config.applications.api.appId = app.appId + + // Update user roles + const [userInfoResponse, userInfoJson] = await config.users.getInfo( + createUserJson.created.successful[0]._id + ) + const prodAppId = db.getProdAppID(app.appId!) + + // Roles must always be set with prod appID + const body: User = { + ...userInfoJson, + roles: { + [prodAppId]: "POWER", + }, + } + await config.users.updateInfo(body) + + const [changedUserInfoResponse, changedUserInfoJson] = + await config.users.getInfo(createUserJson.created.successful[0]._id) + expect(changedUserInfoJson.roles[prodAppId]).toBeDefined() + expect(changedUserInfoJson.roles[prodAppId]).toEqual("POWER") + + await config.screen.create(generateScreen("BASIC")) + await config.screen.create(generateScreen("POWER")) + await config.screen.create(generateScreen("ADMIN")) + + await config.applications.publish(app.appId) + const [firstappPackageResponse, firstappPackageJson] = + await config.applications.getAppPackage(app.appId) + expect(firstappPackageJson.screens).toBeDefined() + expect(firstappPackageJson.screens.length).toEqual(3) + + // login with POWER user + await config.login(appUser[0].email!, appUser[0].password!) + const [selfInfoResponse, selfInfoJson] = await config.users.getSelf() + + // fetch app package + const [appPackageResponse, appPackageJson] = + await config.applications.getAppPackage(app.appId!) + expect(appPackageJson.screens).toBeDefined() + expect(appPackageJson.screens.length).toEqual(2) + }) + + it("Check Screen access for ADMIN role", async () => { + // Set up user + const appUser = generateUser() + expect(appUser[0].builder?.global).toEqual(false) + expect(appUser[0].admin?.global).toEqual(false) + const [createUserResponse, createUserJson] = + await config.users.addMultiple(appUser) + + // Create App + const app = await config.applications.create(generateApp()) + config.applications.api.appId = app.appId + + // Update user roles + const [userInfoResponse, userInfoJson] = await config.users.getInfo( + createUserJson.created.successful[0]._id + ) + const prodAppId = db.getProdAppID(app.appId!) + + // Roles must always be set with prod appID + const body: User = { + ...userInfoJson, + roles: { + [prodAppId]: "ADMIN", + }, + } + await config.users.updateInfo(body) + + const [changedUserInfoResponse, changedUserInfoJson] = + await config.users.getInfo(createUserJson.created.successful[0]._id) + expect(changedUserInfoJson.roles[prodAppId]).toBeDefined() + expect(changedUserInfoJson.roles[prodAppId]).toEqual("ADMIN") + + await config.screen.create(generateScreen("BASIC")) + await config.screen.create(generateScreen("POWER")) + await config.screen.create(generateScreen("ADMIN")) + + await config.applications.publish(app.appId) + const [firstappPackageResponse, firstappPackageJson] = + await config.applications.getAppPackage(app.appId) + expect(firstappPackageJson.screens).toBeDefined() + expect(firstappPackageJson.screens.length).toEqual(3) + + // login with ADMIN user + await config.login(appUser[0].email!, appUser[0].password!) + const [selfInfoResponse, selfInfoJson] = await config.users.getSelf() + + // fetch app package + const [appPackageResponse, appPackageJson] = + await config.applications.getAppPackage(app.appId!) + expect(appPackageJson.screens).toBeDefined() + expect(appPackageJson.screens.length).toEqual(3) + }) + }) + describe("Screen Access for custom roles", () => { + it("Custom role access for level 1 permissions", async () => { + // Set up user + const appUser = generateUser() + expect(appUser[0].builder?.global).toEqual(false) + expect(appUser[0].admin?.global).toEqual(false) + const [createUserResponse, createUserJson] = + await config.users.addMultiple(appUser) + + // Create App + const app = await config.applications.create(generateApp()) + config.applications.api.appId = app.appId + + //Create level 1 role + const role = { + inherits: "BASIC", + permissionId: "public", + name: "level 1", + } + const [createRoleResponse, createRoleJson] = + await config.users.createRole(role) + + // Update user roles + const [userInfoResponse, userInfoJson] = await config.users.getInfo( + createUserJson.created.successful[0]._id + ) + const prodAppId = db.getProdAppID(app.appId!) + + // Roles must always be set with prod appID + const body: User = { + ...userInfoJson, + roles: { + [prodAppId]: createRoleJson._id, + }, + } + await config.users.updateInfo(body) + + const [changedUserInfoResponse, changedUserInfoJson] = + await config.users.getInfo(createUserJson.created.successful[0]._id) + expect(changedUserInfoJson.roles[prodAppId]).toBeDefined() + expect(changedUserInfoJson.roles[prodAppId]).toEqual(createRoleJson._id) + + await config.screen.create(generateScreen("BASIC")) + await config.screen.create(generateScreen("POWER")) + await config.screen.create(generateScreen("ADMIN")) + + await config.applications.publish(app.appId) + const [firstappPackageResponse, firstappPackageJson] = + await config.applications.getAppPackage(app.appId) + expect(firstappPackageJson.screens).toBeDefined() + expect(firstappPackageJson.screens.length).toEqual(3) + + // login with level 1 user + await config.login(appUser[0].email!, appUser[0].password!) + const [selfInfoResponse, selfInfoJson] = await config.users.getSelf() + + // fetch app package + const [appPackageResponse, appPackageJson] = + await config.applications.getAppPackage(app.appId!) + expect(appPackageJson.screens).toBeDefined() + expect(appPackageJson.screens.length).toEqual(1) + }) + it("Custom role access for level 2 permissions", async () => { + // Set up user + const appUser = generateUser() + expect(appUser[0].builder?.global).toEqual(false) + expect(appUser[0].admin?.global).toEqual(false) + const [createUserResponse, createUserJson] = + await config.users.addMultiple(appUser) + + // Create App + const app = await config.applications.create(generateApp()) + config.applications.api.appId = app.appId + + //Create level 1 role + const role = { + inherits: "BASIC", + permissionId: "read_only", + name: "level 2", + } + const [createRoleResponse, createRoleJson] = + await config.users.createRole(role) + + // Update user roles + const [userInfoResponse, userInfoJson] = await config.users.getInfo( + createUserJson.created.successful[0]._id + ) + const prodAppId = db.getProdAppID(app.appId!) + + // Roles must always be set with prod appID + const body: User = { + ...userInfoJson, + roles: { + [prodAppId]: createRoleJson._id, + }, + } + await config.users.updateInfo(body) + + const [changedUserInfoResponse, changedUserInfoJson] = + await config.users.getInfo(createUserJson.created.successful[0]._id) + expect(changedUserInfoJson.roles[prodAppId]).toBeDefined() + expect(changedUserInfoJson.roles[prodAppId]).toEqual(createRoleJson._id) + + await config.screen.create(generateScreen("BASIC")) + await config.screen.create(generateScreen("POWER")) + await config.screen.create(generateScreen("ADMIN")) + + await config.applications.publish(app.appId) + const [firstappPackageResponse, firstappPackageJson] = + await config.applications.getAppPackage(app.appId) + expect(firstappPackageJson.screens).toBeDefined() + expect(firstappPackageJson.screens.length).toEqual(3) + + // login with level 1 user + await config.login(appUser[0].email!, appUser[0].password!) + const [selfInfoResponse, selfInfoJson] = await config.users.getSelf() + + // fetch app package + const [appPackageResponse, appPackageJson] = + await config.applications.getAppPackage(app.appId!) + expect(appPackageJson.screens).toBeDefined() + expect(appPackageJson.screens.length).toEqual(1) + }) + it("Custom role access for level 3 permissions", async () => { + const appUser = generateUser() + expect(appUser[0].builder?.global).toEqual(false) + expect(appUser[0].admin?.global).toEqual(false) + const [createUserResponse, createUserJson] = + await config.users.addMultiple(appUser) + + // Create App + const app = await config.applications.create(generateApp()) + config.applications.api.appId = app.appId + + //Create level 1 role + const role = { + inherits: "BASIC", + permissionId: "write", + name: "level 3", + } + const [createRoleResponse, createRoleJson] = + await config.users.createRole(role) + + // Update user roles + const [userInfoResponse, userInfoJson] = await config.users.getInfo( + createUserJson.created.successful[0]._id + ) + const prodAppId = db.getProdAppID(app.appId!) + + // Roles must always be set with prod appID + const body: User = { + ...userInfoJson, + roles: { + [prodAppId]: createRoleJson._id, + }, + } + await config.users.updateInfo(body) + + const [changedUserInfoResponse, changedUserInfoJson] = + await config.users.getInfo(createUserJson.created.successful[0]._id) + expect(changedUserInfoJson.roles[prodAppId]).toBeDefined() + expect(changedUserInfoJson.roles[prodAppId]).toEqual(createRoleJson._id) + + await config.screen.create(generateScreen("BASIC")) + await config.screen.create(generateScreen("POWER")) + await config.screen.create(generateScreen("ADMIN")) + + await config.applications.publish(app.appId) + const [firstappPackageResponse, firstappPackageJson] = + await config.applications.getAppPackage(app.appId) + expect(firstappPackageJson.screens).toBeDefined() + expect(firstappPackageJson.screens.length).toEqual(3) + + // login with level 1 user + await config.login(appUser[0].email!, appUser[0].password!) + const [selfInfoResponse, selfInfoJson] = await config.users.getSelf() + + // fetch app package + const [appPackageResponse, appPackageJson] = + await config.applications.getAppPackage(app.appId!) + expect(appPackageJson.screens).toBeDefined() + expect(appPackageJson.screens.length).toEqual(1) + }) + it("Custom role access for level 4 permissions", async () => { + const appUser = generateUser() + expect(appUser[0].builder?.global).toEqual(false) + expect(appUser[0].admin?.global).toEqual(false) + const [createUserResponse, createUserJson] = + await config.users.addMultiple(appUser) + + // Create App + const app = await config.applications.create(generateApp()) + config.applications.api.appId = app.appId + + //Create level 1 role + const role = { + inherits: "BASIC", + permissionId: "power", + name: "level 4", + } + const [createRoleResponse, createRoleJson] = + await config.users.createRole(role) + + // Update user roles + const [userInfoResponse, userInfoJson] = await config.users.getInfo( + createUserJson.created.successful[0]._id + ) + const prodAppId = db.getProdAppID(app.appId!) + + // Roles must always be set with prod appID + const body: User = { + ...userInfoJson, + roles: { + [prodAppId]: createRoleJson._id, + }, + } + await config.users.updateInfo(body) + + const [changedUserInfoResponse, changedUserInfoJson] = + await config.users.getInfo(createUserJson.created.successful[0]._id) + expect(changedUserInfoJson.roles[prodAppId]).toBeDefined() + expect(changedUserInfoJson.roles[prodAppId]).toEqual(createRoleJson._id) + + await config.screen.create(generateScreen("BASIC")) + await config.screen.create(generateScreen("POWER")) + await config.screen.create(generateScreen("ADMIN")) + + await config.applications.publish(app.appId) + const [firstappPackageResponse, firstappPackageJson] = + await config.applications.getAppPackage(app.appId) + expect(firstappPackageJson.screens).toBeDefined() + expect(firstappPackageJson.screens.length).toEqual(3) + + // login with level 1 user + await config.login(appUser[0].email!, appUser[0].password!) + const [selfInfoResponse, selfInfoJson] = await config.users.getSelf() + + // fetch app package + const [appPackageResponse, appPackageJson] = + await config.applications.getAppPackage(app.appId!) + expect(appPackageJson.screens).toBeDefined() + expect(appPackageJson.screens.length).toEqual(1) + }) + it("Custom role access for level 5 permissions", async () => { + const appUser = generateUser() + expect(appUser[0].builder?.global).toEqual(false) + expect(appUser[0].admin?.global).toEqual(false) + const [createUserResponse, createUserJson] = + await config.users.addMultiple(appUser) + + // Create App + const app = await config.applications.create(generateApp()) + config.applications.api.appId = app.appId + + //Create level 1 role + const role = { + inherits: "BASIC", + permissionId: "admin", + name: "level 5", + } + const [createRoleResponse, createRoleJson] = + await config.users.createRole(role) + + // Update user roles + const [userInfoResponse, userInfoJson] = await config.users.getInfo( + createUserJson.created.successful[0]._id + ) + const prodAppId = db.getProdAppID(app.appId!) + + // Roles must always be set with prod appID + const body: User = { + ...userInfoJson, + roles: { + [prodAppId]: createRoleJson._id, + }, + } + await config.users.updateInfo(body) + + const [changedUserInfoResponse, changedUserInfoJson] = + await config.users.getInfo(createUserJson.created.successful[0]._id) + expect(changedUserInfoJson.roles[prodAppId]).toBeDefined() + expect(changedUserInfoJson.roles[prodAppId]).toEqual(createRoleJson._id) + + await config.screen.create(generateScreen("BASIC")) + await config.screen.create(generateScreen("POWER")) + await config.screen.create(generateScreen("ADMIN")) + + await config.applications.publish(app.appId) + const [firstappPackageResponse, firstappPackageJson] = + await config.applications.getAppPackage(app.appId) + expect(firstappPackageJson.screens).toBeDefined() + expect(firstappPackageJson.screens.length).toEqual(3) + + // login with level 1 user + await config.login(appUser[0].email!, appUser[0].password!) + const [selfInfoResponse, selfInfoJson] = await config.users.getSelf() + + // fetch app package + const [appPackageResponse, appPackageJson] = + await config.applications.getAppPackage(app.appId!) + expect(appPackageJson.screens).toBeDefined() + expect(appPackageJson.screens.length).toEqual(1) + }) + }) +}) diff --git a/qa-core/src/tests/internal-api/userManagement/userManagement.spec.ts b/qa-core/src/tests/internal-api/userManagement/userManagement.spec.ts new file mode 100644 index 0000000000..ea235d7cc7 --- /dev/null +++ b/qa-core/src/tests/internal-api/userManagement/userManagement.spec.ts @@ -0,0 +1,88 @@ +import TestConfiguration from "../../../config/internal-api/TestConfiguration" +import { Application } from "@budibase/server/api/controllers/public/mapping/types" +import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient" +import { generateUser } from "../../../config/internal-api/fixtures/userManagement" +import { User } from "@budibase/types" + +describe("Internal API - User Management & Permissions", () => { + const api = new InternalAPIClient() + const config = new TestConfiguration(api) + + // Before each test, login as admin. Some tests will require login as a different user + beforeEach(async () => { + await config.loginAsAdmin() + }) + + afterAll(async () => { + await config.afterAll() + }) + + it("Add Users with different roles", async () => { + await config.users.search() + await config.users.getRoles() + + const admin = generateUser(1, "admin") + expect(admin[0].builder?.global).toEqual(true) + expect(admin[0].admin?.global).toEqual(true) + const developer = generateUser(1, "developer") + expect(developer[0].builder?.global).toEqual(true) + const appUser = generateUser(1, "appUser") + expect(appUser[0].builder?.global).toEqual(false) + expect(appUser[0].admin?.global).toEqual(false) + + const userList = [...admin, ...developer, ...appUser] + + await config.users.addMultiple(userList) + + const [allUsersResponse, allUsersJson] = await config.users.getAll() + expect(allUsersJson.length).toBeGreaterThan(0) + }) + + it("Delete User", async () => { + const appUser = generateUser() + expect(appUser[0].builder?.global).toEqual(false) + expect(appUser[0].admin?.global).toEqual(false) + const [userResponse, userJson] = await config.users.addMultiple(appUser) + const userId = userJson.created.successful[0]._id + await config.users.delete(userId) + }) + + it("Reset Password", async () => { + const appUser = generateUser() + expect(appUser[0].builder?.global).toEqual(false) + expect(appUser[0].admin?.global).toEqual(false) + const [userResponse, userJson] = await config.users.addMultiple(appUser) + const [userInfoResponse, userInfoJson] = await config.users.getInfo( + userJson.created.successful[0]._id + ) + const body: User = { + ...userInfoJson, + password: "newPassword", + } + await config.users.forcePasswordReset(body) + }) + + it("Change User information", async () => { + const appUser = generateUser() + expect(appUser[0].builder?.global).toEqual(false) + expect(appUser[0].admin?.global).toEqual(false) + const [userResponse, userJson] = await config.users.addMultiple(appUser) + const [userInfoResponse, userInfoJson] = await config.users.getInfo( + userJson.created.successful[0]._id + ) + const body: User = { + ...userInfoJson, + firstName: "newFirstName", + lastName: "newLastName", + builder: { + global: true, + }, + } + await config.users.updateInfo(body) + + const [changedUserInfoResponse, changedUserInfoJson] = + await config.users.getInfo(userJson.created.successful[0]._id) + expect(changedUserInfoJson.builder?.global).toBeDefined() + expect(changedUserInfoJson.builder?.global).toEqual(true) + }) +}) diff --git a/qa-core/src/tests/public-api/applications/applications.spec.ts b/qa-core/src/tests/public-api/applications/applications.spec.ts index 05eaca6177..cf85e6daf2 100644 --- a/qa-core/src/tests/public-api/applications/applications.spec.ts +++ b/qa-core/src/tests/public-api/applications/applications.spec.ts @@ -2,6 +2,7 @@ import TestConfiguration from "../../../config/public-api/TestConfiguration" import PublicAPIClient from "../../../config/public-api/TestConfiguration/PublicAPIClient" import generateApp from "../../../config/public-api/fixtures/applications" import { Application } from "@budibase/server/api/controllers/public/mapping/types" +import { db as dbCore } from "@budibase/backend-core" describe("Public API - /applications endpoints", () => { const api = new PublicAPIClient() @@ -47,4 +48,50 @@ describe("Public API - /applications endpoints", () => { expect(app.updatedAt).not.toEqual(config.context.updatedAt) expect(app.name).toEqual(config.context.name) }) + + it("POST - publish an application", async () => { + config.context.name = "UpdatedName" + const [response, deployment] = await config.applications.publish( + config.context._id + ) + expect(response).toHaveStatusCode(200) + expect(deployment).toEqual({ + status: "SUCCESS", + }) + + // Verify publish + const prodAppId = dbCore.getProdAppID(config.context._id) + const [_, publishedApp] = await config.applications.read(prodAppId) + expect(response).toHaveStatusCode(200) + expect(publishedApp._id).toEqual(prodAppId) + }) + + it("POST - unpublish a published application", async () => { + await config.applications.publish(config.context._id) + const [response] = await config.applications.unpublish(config.context._id) + expect(response).toHaveStatusCode(204) + }) + + it("POST - unpublish an unpublished application", async () => { + const [response] = await config.applications.unpublish(config.context._id) + expect(response).toHaveStatusCode(400) + }) + + it("DELETE - delete a published application and the dev application", async () => { + await config.applications.publish(config.context._id) + const [response, deletion] = await config.applications.delete( + config.context._id + ) + expect(response).toHaveStatusCode(200) + expect(deletion._id).toEqual(config.context._id) + + // verify dev app deleted + const [devAppResponse] = await config.applications.read(config.context._id) + expect(devAppResponse).toHaveStatusCode(404) + + // verify prod app deleted + const prodAppId = dbCore.getProdAppID(config.context._id) + const [publishedAppResponse] = await config.applications.read(prodAppId) + expect(publishedAppResponse).toHaveStatusCode(404) + }) }) diff --git a/scripts/link-dependencies.sh b/scripts/link-dependencies.sh index 4485c4924f..d2a501162b 100755 --- a/scripts/link-dependencies.sh +++ b/scripts/link-dependencies.sh @@ -1,25 +1,30 @@ echo "Linking backend-core" cd packages/backend-core +yarn unlink yarn link cd - echo "Linking string-templates" -cd packages/string-templates +cd packages/string-templates +yarn unlink yarn link cd - echo "Linking types" -cd packages/types +cd packages/types +yarn unlink yarn link cd - echo "Linking bbui" -cd packages/bbui +cd packages/bbui +yarn unlink yarn link cd - echo "Linking frontend-core" cd packages/frontend-core +yarn unlink yarn link cd - @@ -30,6 +35,7 @@ if [ -d "../budibase-pro" ]; then cd packages/pro echo "Linking pro" + yarn unlink yarn link echo "Linking backend-core to pro" diff --git a/scripts/pro/build.sh b/scripts/pro/build.sh new file mode 100755 index 0000000000..b03d85a0d0 --- /dev/null +++ b/scripts/pro/build.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# This script is designed for building the pro repo after the backend-core build has completed. +# This ensures that any changes in core that are required by pro are done in the correct order. + +set -e + +# Go to parent of budibase +cd ../../../ + +if [[ -d "budibase-pro" ]]; then + cd budibase-pro + echo "Building pro" + yarn build +fi \ No newline at end of file diff --git a/scripts/pro/install.sh b/scripts/pro/install.sh index ee80bf9e0d..d6662d6341 100755 --- a/scripts/pro/install.sh +++ b/scripts/pro/install.sh @@ -35,5 +35,5 @@ if [[ -d "budibase-pro" ]]; then git pull echo "Initializing pro repo..." - yarn setup + yarn fi \ No newline at end of file diff --git a/scripts/proxy/generateProxyConfig.js b/scripts/proxy/generateProxyConfig.js deleted file mode 100644 index e30daf6156..0000000000 --- a/scripts/proxy/generateProxyConfig.js +++ /dev/null @@ -1,111 +0,0 @@ -#!/usr/bin/env node -const path = require("path") -const fs = require("fs") - -function processStringSync(string, env) { - let output = "" - - // process if statements - let removal = false - for (let line of string.split("\n")) { - if (new RegExp(`{{\/if}}`, "g").test(line)) { - removal = false - continue - } - - if (!removal) { - const match = line.match(new RegExp(`{{#if (.*)}}`)) - if (match) { - const key = match[1] - // check the if statement is true - if (!env[key]) { - removal = true - } - continue - } - output += line + "\n" - } - } - - for (let key in env) { - // replace variables - const rgx = new RegExp(`{{\\s*${key}\\s*}}`, "g") - output = output.replace(rgx, env[key]) - } - - return output -} - -const Configs = { - prod: { - apps: "app-service.budibase.svc.cluster.local", - worker: "worker-service.budibase.svc.cluster.local", - minio: "minio-service.budibase.svc.cluster.local", - couchdb: "budibase-prod-svc-couchdb", - resolver: "kube-dns.kube-system.svc.cluster.local" - }, - preprod: { - apps: "app-service.budibase.svc.cluster.local", - worker: "worker-service.budibase.svc.cluster.local", - minio: "minio-service.budibase.svc.cluster.local", - couchdb: "budibase-preprod-svc-couchdb", - resolver: "kube-dns.kube-system.svc.cluster.local" - }, - release: { - apps: "app-service.budibase.svc.cluster.local", - worker: "worker-service.budibase.svc.cluster.local", - minio: "minio-service.budibase.svc.cluster.local", - couchdb: "budibase-release-svc-couchdb", - resolver: "kube-dns.kube-system.svc.cluster.local" - }, - compose: { - apps: "app-service", - worker: "worker-service", - minio: "minio-service", - couchdb: "couchdb-service", - watchtower: "watchtower-service", - resolver: "127.0.0.11" - }, -} - -const Commands = { - Prod: "prod", - Preprod: "preprod", - Release: "release", - Compose: "compose", -} - -async function init(managementCommand) { - const config = Configs[managementCommand] - const hostingPath = path.join(process.cwd(), "hosting") - const nginxHbsPath = path.join(hostingPath, "nginx.prod.conf.hbs") - const nginxOutputPath = path.join( - hostingPath, - "proxy", - ".generated-nginx.prod.conf" - ) - const contents = fs.readFileSync(nginxHbsPath, "utf8") - fs.writeFileSync(nginxOutputPath, processStringSync(contents, config)) -} - -const managementCommand = process.argv.slice(2)[0] - -if ( - !managementCommand || - !Object.values(Commands).some(command => managementCommand === command) -) { - throw new Error( - "You must supply either a 'compose', 'preprod' or 'prod' commmand to generate an NGINX config." - ) -} - -init(managementCommand) - .then(() => { - console.log("Done! 🎉") - }) - .catch(err => { - console.error( - "Something went wrong while creating the nginx configuration", - err.message - ) - })