diff --git a/.eslintignore b/.eslintignore index 68d88aaaf1..91f5433596 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,4 +4,6 @@ dist packages/server/builder packages/server/coverage packages/server/client -packages/builder/.routify \ No newline at end of file +packages/builder/.routify +packages/builder/cypress/support/queryLevelTransformerFunction.js +packages/builder/cypress/support/queryLevelTransformerFunctionWithData.js \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index 4dc11c0d65..87f8269c50 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -24,9 +24,28 @@ { "files": ["*.svelte"], "processor": "svelte3/svelte3" + }, + { + "files": ["**/*.ts"], + "parser": "@typescript-eslint/parser", + "plugins": [], + "extends": [ + "eslint:recommended" + ], + "rules": { + "no-unused-vars": "off", + "no-inner-declarations": "off", + "no-case-declarations": "off", + "no-useless-escape": "off", + "no-undef": "off", + "no-prototype-builtins": "off" + } } ], "rules": { "no-self-assign": "off" + }, + "globals": { + "GeolocationPositionError": true } } diff --git a/.github/workflows/budibase_ci.yml b/.github/workflows/budibase_ci.yml index 7e95115415..cb27b30f3f 100644 --- a/.github/workflows/budibase_ci.yml +++ b/.github/workflows/budibase_ci.yml @@ -43,4 +43,8 @@ jobs: verbose: true # TODO: parallelise this - - run: yarn test:e2e:ci + - name: Cypress run + uses: cypress-io/github-action@v2 + with: + install: false + command: yarn test:e2e:ci diff --git a/.github/workflows/deploy-cloud.yaml b/.github/workflows/deploy-cloud.yaml index d54e6c9c68..db03165a91 100644 --- a/.github/workflows/deploy-cloud.yaml +++ b/.github/workflows/deploy-cloud.yaml @@ -38,6 +38,17 @@ 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 10fadc36c5..ac76e9bed8 100644 --- a/.github/workflows/deploy-preprod.yml +++ b/.github/workflows/deploy-preprod.yml @@ -23,12 +23,24 @@ jobs: aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: eu-west-1 + - name: Get the latest budibase release version id: version run: | release_version=$(cat lerna.json | jq -r '.version') echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV + - name: Tag and release Proxy service docker image + run: | + docker login -u $DOCKER_USER -p $DOCKER_PASSWORD + yarn build:docker:proxy: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/release-selfhost.yml b/.github/workflows/release-selfhost.yml index f51648074c..9f42a9cc5d 100644 --- a/.github/workflows/release-selfhost.yml +++ b/.github/workflows/release-selfhost.yml @@ -47,6 +47,13 @@ jobs: yarn yarn build popd + + - name: Build OpenAPI spec + run: | + pushd packages/server + yarn + yarn specs + popd - name: Setup Helm uses: azure/setup-helm@v1 @@ -56,6 +63,7 @@ jobs: run: | git config user.name "Budibase Helm Bot" git config user.email "<>" + git reset --hard git pull helm package charts/budibase git checkout gh-pages @@ -77,3 +85,5 @@ jobs: packages/cli/build/cli-win.exe packages/cli/build/cli-linux packages/cli/build/cli-macos + packages/server/specs/openapi.yaml + packages/server/specs/openapi.json diff --git a/.gitignore b/.gitignore index abf2a78766..7d09f0a2ba 100644 --- a/.gitignore +++ b/.gitignore @@ -96,3 +96,6 @@ hosting/proxy/.generated-nginx.prod.conf *.sublime-workspace bin/ +hosting/.generated* +packages/builder/cypress.env.json +stats.html diff --git a/.prettierignore b/.prettierignore index 522d218640..bbeff65da7 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,9 +1,11 @@ node_modules -public dist *.spec.js packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte packages/server/builder packages/server/coverage packages/server/client -packages/builder/.routify \ No newline at end of file +packages/server/src/definitions/openapi.ts +packages/builder/.routify +packages/builder/cypress/support/queryLevelTransformerFunction.js +packages/builder/cypress/support/queryLevelTransformerFunctionWithData.js diff --git a/README.md b/README.md index aa368d29fd..17a3ab1ef2 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ The low code platform you'll enjoy using

- Budibase is an open source low-code platform, and the easiest way to build internal tools that improve productivity. + Budibase is an open source low-code platform, and the easiest way to build internal apps that improve productivity.

@@ -40,9 +40,11 @@

- Get started + Get started - we host (Budibase Cloud) · - Docs + Get started - you host (Docker, K8s, DO) + · + Docs · Feature request · @@ -100,16 +102,45 @@ Budibase is made to scale. With Budibase, you can self-host on your own infrastr - Checkout the promo video: https://youtu.be/xoljVpty_Kw +
+ +--- + +
+ + +## Budibase Public API +As with anything that we build in Budibase, our new public API is simple to use, flexible, and introduces new extensibility. To summarize, the Budibase API enables: + +- Budibase as a backend +- Interoperability + + +#### Docs +You can learn more about the Budibase API at the following places: + +- [General documentation](https://docs.budibase.com/docs/public-api) : Learn how to get your API key, how to use spec, and how to use with Postman +- [Interactive API documentation](https://docs.budibase.com/reference/post_applications) : Learn how to interact with the API + +#### Guides + +- [Build an app with Budibase and Next.js](https://budibase.com/blog/building-a-crud-app-with-budibase-and-next.js/) + +

+ Budibase data +

+

+


## 🏁 Get started - + Deploy Budibase self-hosted in your existing infrastructure, using Docker, Kubernetes, and Digital Ocean. Or use Budibase Cloud if you don't need to self-host, and would like to get started quickly. -### [Get started with self-hosting Budibase](https://docs.budibase.com/self-hosting/self-host) +### [Get started with self-hosting Budibase](https://docs.budibase.com/docs/hosting-methods) ### [Get started with Budibase Cloud](https://budibase.com) @@ -118,7 +149,7 @@ Or use Budibase Cloud if you don't need to self-host, and would like to get star ## 🎓 Learning Budibase -The Budibase documentation [lives here](https://docs.budibase.com). +The Budibase documentation [lives here](https://docs.budibase.com/docs).
diff --git a/examples/nextjs-api-sales/.eslintrc.json b/examples/nextjs-api-sales/.eslintrc.json new file mode 100644 index 0000000000..bffb357a71 --- /dev/null +++ b/examples/nextjs-api-sales/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/examples/nextjs-api-sales/.gitignore b/examples/nextjs-api-sales/.gitignore new file mode 100644 index 0000000000..7d093c39ff --- /dev/null +++ b/examples/nextjs-api-sales/.gitignore @@ -0,0 +1,38 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +.env.local +.env.development.local +.env.test.local +.env.production.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo diff --git a/examples/nextjs-api-sales/README.md b/examples/nextjs-api-sales/README.md new file mode 100644 index 0000000000..8a466206e7 --- /dev/null +++ b/examples/nextjs-api-sales/README.md @@ -0,0 +1,41 @@ +# Budibase API + Next.js example + +This is an example of how Budibase can be used as a backend for a Postgres database for a Next.js sales app. You will +need to follow the walk-through that has been published in the Budibase docs to set up your Budibase app for this example. + +## Pre-requisites + +To use this example you will need: +1. [Docker](https://www.docker.com/) +2. [Docker Compose](https://docs.docker.com/compose/) +3. [Node.js](https://nodejs.org/en/) +4. A self-hosted Budibase installation + +## Getting Started + +The first step is to set up the database - you can do this by going to the `db/` directory and running the command: + +```bash +docker-compose up +``` + +The next step is to follow the example walk-through and set up a Budibase app as it describes. Once you've done +this you can configure the settings in `next.config.js`, specifically the `apiKey`, `host` and `appName`. + +Finally, you can start the dev server with the following command: + +```bash +npm run dev +# or +yarn dev +``` + +## Accessing the app + +Open [http://localhost:3001](http://localhost:3001) with your browser to see the sales app. + +Look in the API routes (`pages/api/sales.ts` and `pages/api/salespeople.ts`) to see how this is integrated with Budibase. +There is also a utility file where some core functions and types have been defined, in `utilities/index.ts`. + +## Attribution +This example was set up using [Next.js](https://nextjs.org/) and bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). diff --git a/examples/nextjs-api-sales/components/layout.tsx b/examples/nextjs-api-sales/components/layout.tsx new file mode 100644 index 0000000000..48052028d7 --- /dev/null +++ b/examples/nextjs-api-sales/components/layout.tsx @@ -0,0 +1,42 @@ +import Link from "next/link" +import Image from "next/image" +import { ReactNotifications } from "react-notifications-component" + +function layout(props: any) { + return ( + <> + + + {props.children} + + ) +} + +export default layout \ No newline at end of file diff --git a/examples/nextjs-api-sales/components/notifications.ts b/examples/nextjs-api-sales/components/notifications.ts new file mode 100644 index 0000000000..991f3f6cf2 --- /dev/null +++ b/examples/nextjs-api-sales/components/notifications.ts @@ -0,0 +1,28 @@ +import { Store } from "react-notifications-component" + +const notifications = { + error: (error: string, title: string) => { + Store.addNotification({ + container: "top-right", + type: "danger", + message: error, + title: title, + dismiss: { + duration: 10000, + }, + }) + }, + success: (message: string, title: string) => { + Store.addNotification({ + container: "top-right", + type: "success", + message: message, + title: title, + dismiss: { + duration: 3000, + }, + }) + }, +} + +export default notifications diff --git a/examples/nextjs-api-sales/db/docker-compose.yml b/examples/nextjs-api-sales/db/docker-compose.yml new file mode 100644 index 0000000000..fdf189911f --- /dev/null +++ b/examples/nextjs-api-sales/db/docker-compose.yml @@ -0,0 +1,17 @@ +version: "3.8" +services: + db: + container_name: postgres + image: postgres + restart: always + environment: + POSTGRES_USER: root + POSTGRES_PASSWORD: root + POSTGRES_DB: postgres + ports: + - "5432:5432" + volumes: + - pg_data:/var/lib/postgresql/data/ + - ./init.sql:/docker-entrypoint-initdb.d/init.sql +volumes: + pg_data: diff --git a/examples/nextjs-api-sales/db/init.sql b/examples/nextjs-api-sales/db/init.sql new file mode 100644 index 0000000000..f0a0c9d100 --- /dev/null +++ b/examples/nextjs-api-sales/db/init.sql @@ -0,0 +1,21 @@ +CREATE TABLE IF NOT EXISTS sales_people ( + person_id SERIAL PRIMARY KEY, + name varchar(200) NOT NULL +); + +CREATE TABLE IF NOT EXISTS sales ( + sale_id SERIAL PRIMARY KEY, + sale_name varchar(200) NOT NULL, + sold_by INT, + CONSTRAINT sold_by_fk + FOREIGN KEY(sold_by) + REFERENCES sales_people(person_id) +); + +INSERT INTO sales_people (name) +select 'Salesperson ' || id +FROM GENERATE_SERIES(1, 50) as id; + +INSERT INTO sales (sale_name, sold_by) +select 'Sale ' || id, floor(random() * 50 + 1)::int +FROM GENERATE_SERIES(1, 200) as id; diff --git a/examples/nextjs-api-sales/definitions/index.ts b/examples/nextjs-api-sales/definitions/index.ts new file mode 100644 index 0000000000..92cdd00571 --- /dev/null +++ b/examples/nextjs-api-sales/definitions/index.ts @@ -0,0 +1,7 @@ +import { components } from "./openapi" + +export type App = components["schemas"]["applicationOutput"]["data"] +export type Table = components["schemas"]["tableOutput"]["data"] +export type TableSearch = components["schemas"]["tableSearch"] +export type AppSearch = components["schemas"]["applicationSearch"] +export type RowSearch = components["schemas"]["searchOutput"] diff --git a/examples/nextjs-api-sales/definitions/openapi.ts b/examples/nextjs-api-sales/definitions/openapi.ts new file mode 100644 index 0000000000..4f4ad45fc6 --- /dev/null +++ b/examples/nextjs-api-sales/definitions/openapi.ts @@ -0,0 +1,1125 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/applications": { + post: { + parameters: { + header: { + /** The ID of the app which this request is targeting. */ + "x-budibase-app-id": components["parameters"]["appId"] + } + } + responses: { + /** Returns the created application. */ + 200: { + content: { + "application/json": components["schemas"]["applicationOutput"] + } + } + } + requestBody: { + content: { + "application/json": components["schemas"]["application"] + } + } + } + } + "/applications/{appId}": { + get: { + parameters: { + path: { + /** The ID of the app which this request is targeting. */ + appId: components["parameters"]["appIdUrl"] + } + } + responses: { + /** Returns the retrieved application. */ + 200: { + content: { + "application/json": components["schemas"]["applicationOutput"] + } + } + } + } + put: { + parameters: { + path: { + /** The ID of the app which this request is targeting. */ + appId: components["parameters"]["appIdUrl"] + } + } + responses: { + /** Returns the updated application. */ + 200: { + content: { + "application/json": components["schemas"]["applicationOutput"] + } + } + } + requestBody: { + content: { + "application/json": components["schemas"]["application"] + } + } + } + delete: { + parameters: { + path: { + /** The ID of the app which this request is targeting. */ + appId: components["parameters"]["appIdUrl"] + } + } + responses: { + /** Returns the deleted application. */ + 200: { + content: { + "application/json": components["schemas"]["applicationOutput"] + } + } + } + } + } + "/applications/search": { + /** Based on application properties (currently only name) search for applications. */ + post: { + parameters: { + header: { + /** The ID of the app which this request is targeting. */ + "x-budibase-app-id": components["parameters"]["appId"] + } + } + responses: { + /** Returns the applications that were found based on the search parameters. */ + 200: { + content: { + "application/json": components["schemas"]["applicationSearch"] + } + } + } + requestBody: { + content: { + "application/json": components["schemas"]["nameSearch"] + } + } + } + } + "/queries/{queryId}": { + /** Queries which have been created within a Budibase app can be executed using this, */ + post: { + parameters: { + path: { + /** The ID of the query which this request is targeting. */ + queryId: components["parameters"]["queryId"] + } + header: { + /** The ID of the app which this request is targeting. */ + "x-budibase-app-id": components["parameters"]["appId"] + } + } + responses: { + /** Returns the result of the query execution. */ + 200: { + content: { + "application/json": components["schemas"]["executeQueryOutput"] + } + } + } + requestBody: { + content: { + "application/json": components["schemas"]["executeQuery"] + } + } + } + } + "/queries/search": { + /** Based on query properties (currently only name) search for queries. */ + post: { + parameters: { + header: { + /** The ID of the app which this request is targeting. */ + "x-budibase-app-id": components["parameters"]["appId"] + } + } + responses: { + /** Returns the queries found based on the search parameters. */ + 200: { + content: { + "application/json": components["schemas"]["querySearch"] + } + } + } + requestBody: { + content: { + "application/json": components["schemas"]["nameSearch"] + } + } + } + } + "/tables/{tableId}/rows": { + /** Creates a row within the specified table. */ + post: { + parameters: { + path: { + /** The ID of the table which this request is targeting. */ + tableId: components["parameters"]["tableId"] + } + header: { + /** The ID of the app which this request is targeting. */ + "x-budibase-app-id": components["parameters"]["appId"] + } + } + responses: { + /** Returns the created row, including the ID which has been generated for it. This can be found in the Budibase portal, viewed under the developer information. */ + 200: { + content: { + "application/json": components["schemas"]["rowOutput"] + } + } + } + requestBody: { + content: { + "application/json": components["schemas"]["row"] + } + } + } + } + "/tables/{tableId}/rows/{rowId}": { + /** This gets a single row, it will be enriched with the full related rows, rather than the squashed "primaryDisplay" format returned by the search endpoint. */ + get: { + parameters: { + path: { + /** The ID of the table which this request is targeting. */ + tableId: components["parameters"]["tableId"] + /** The ID of the row which this request is targeting. */ + rowId: components["parameters"]["rowId"] + } + header: { + /** The ID of the app which this request is targeting. */ + "x-budibase-app-id": components["parameters"]["appId"] + } + } + responses: { + /** Returns the retrieved row. */ + 200: { + content: { + "application/json": components["schemas"]["rowOutput"] + } + } + } + } + /** Updates a row within the specified table. */ + put: { + parameters: { + path: { + /** The ID of the table which this request is targeting. */ + tableId: components["parameters"]["tableId"] + /** The ID of the row which this request is targeting. */ + rowId: components["parameters"]["rowId"] + } + header: { + /** The ID of the app which this request is targeting. */ + "x-budibase-app-id": components["parameters"]["appId"] + } + } + responses: { + /** Returns the created row, including the ID which has been generated for it. */ + 200: { + content: { + "application/json": components["schemas"]["rowOutput"] + } + } + } + requestBody: { + content: { + "application/json": components["schemas"]["row"] + } + } + } + /** Deletes a row within the specified table. */ + delete: { + parameters: { + path: { + /** The ID of the table which this request is targeting. */ + tableId: components["parameters"]["tableId"] + /** The ID of the row which this request is targeting. */ + rowId: components["parameters"]["rowId"] + } + header: { + /** The ID of the app which this request is targeting. */ + "x-budibase-app-id": components["parameters"]["appId"] + } + } + responses: { + /** Returns the deleted row, including the ID which has been generated for it. */ + 200: { + content: { + "application/json": components["schemas"]["rowOutput"] + } + } + } + } + } + "/tables/{tableId}/rows/search": { + post: { + parameters: { + path: { + /** The ID of the table which this request is targeting. */ + tableId: components["parameters"]["tableId"] + } + header: { + /** The ID of the app which this request is targeting. */ + "x-budibase-app-id": components["parameters"]["appId"] + } + } + responses: { + /** The response will contain an array of rows that match the search parameters. */ + 200: { + content: { + "application/json": components["schemas"]["searchOutput"] + } + } + } + requestBody: { + content: { + "application/json": { + query: { + /** + * @description A map of field name to the string to search for, this will look for rows that have a value starting with the string value. + * @example [object Object] + */ + string?: { [key: string]: string } + /** @description A fuzzy search, only supported by internal tables. */ + fuzzy?: { [key: string]: unknown } + /** + * @description Searches within a range, the format of this must be columnName -> [low, high]. + * @example [object Object] + */ + range?: { [key: string]: unknown } + /** @description Searches for rows that have a column value that is exactly the value set. */ + equal?: { [key: string]: unknown } + /** @description Searches for any row which does not contain the specified column value. */ + notEqual?: { [key: string]: unknown } + /** + * @description Searches for rows which do not contain the specified column. The object should simply contain keys of the column names, these can map to any value. + * @example [object Object] + */ + empty?: { [key: string]: unknown } + /** @description Searches for rows which have the specified column. */ + notEmpty?: { [key: string]: unknown } + /** @description Searches for rows which have a column value that is any of the specified values. The format of this must be columnName -> [value1, value2]. */ + oneOf?: { [key: string]: unknown } + } + /** @description Enables pagination, by default this is disabled. */ + paginate?: boolean + /** @description If retrieving another page, the bookmark from the previous request must be supplied. */ + bookmark?: string | number + /** @description The maximum number of rows to return, useful when paginating, for internal tables this will be limited to 1000, for SQL tables it will be 5000. */ + limit?: number + /** @description A set of parameters describing the sort behaviour of the search. */ + sort?: { + /** + * @description The order of the sort, by default this is ascending. + * @enum {string} + */ + order?: "ascending" | "descending" + /** @description The name of the column by which the rows will be sorted. */ + column?: string + /** + * @description Defines whether the column should be treated as a string or as numbers when sorting. + * @enum {string} + */ + type?: "string" | "number" + } + } + } + } + } + } + "/tables": { + /** Create a table, this could be internal or external. */ + post: { + parameters: { + header: { + /** The ID of the app which this request is targeting. */ + "x-budibase-app-id": components["parameters"]["appId"] + } + } + responses: { + /** Returns the created table, including the ID which has been generated for it. This can be internal or external data sources. */ + 200: { + content: { + "application/json": components["schemas"]["tableOutput"] + } + } + } + requestBody: { + content: { + "application/json": components["schemas"]["table"] + } + } + } + } + "/tables/{tableId}": { + /** Lookup a table, this could be internal or external. */ + get: { + parameters: { + path: { + /** The ID of the table which this request is targeting. */ + tableId: components["parameters"]["tableId"] + } + header: { + /** The ID of the app which this request is targeting. */ + "x-budibase-app-id": components["parameters"]["appId"] + } + } + responses: { + /** Returns the retrieved table. */ + 200: { + content: { + "application/json": components["schemas"]["tableOutput"] + } + } + } + } + /** Update a table, this could be internal or external. */ + put: { + parameters: { + path: { + /** The ID of the table which this request is targeting. */ + tableId: components["parameters"]["tableId"] + } + header: { + /** The ID of the app which this request is targeting. */ + "x-budibase-app-id": components["parameters"]["appId"] + } + } + responses: { + /** Returns the updated table. */ + 200: { + content: { + "application/json": components["schemas"]["tableOutput"] + } + } + } + requestBody: { + content: { + "application/json": components["schemas"]["table"] + } + } + } + /** Delete a table, this could be internal or external. */ + delete: { + parameters: { + path: { + /** The ID of the table which this request is targeting. */ + tableId: components["parameters"]["tableId"] + } + header: { + /** The ID of the app which this request is targeting. */ + "x-budibase-app-id": components["parameters"]["appId"] + } + } + responses: { + /** Returns the deleted table. */ + 200: { + content: { + "application/json": components["schemas"]["tableOutput"] + } + } + } + } + } + "/tables/search": { + /** Based on table properties (currently only name) search for tables. This could be an internal or an external table. */ + post: { + parameters: { + header: { + /** The ID of the app which this request is targeting. */ + "x-budibase-app-id": components["parameters"]["appId"] + } + } + responses: { + /** Returns the found tables, based on the search parameters. */ + 200: { + content: { + "application/json": components["schemas"]["tableSearch"] + } + } + } + requestBody: { + content: { + "application/json": components["schemas"]["nameSearch"] + } + } + } + } + "/users": { + post: { + responses: { + /** Returns the created user. */ + 200: { + content: { + "application/json": components["schemas"]["userOutput"] + } + } + } + requestBody: { + content: { + "application/json": components["schemas"]["user"] + } + } + } + } + "/users/{userId}": { + get: { + parameters: { + path: { + /** The ID of the user which this request is targeting. */ + userId: components["parameters"]["userId"] + } + } + responses: { + /** Returns the retrieved user. */ + 200: { + content: { + "application/json": components["schemas"]["userOutput"] + } + } + } + } + put: { + parameters: { + path: { + /** The ID of the user which this request is targeting. */ + userId: components["parameters"]["userId"] + } + } + responses: { + /** Returns the updated user. */ + 200: { + content: { + "application/json": components["schemas"]["userOutput"] + } + } + } + requestBody: { + content: { + "application/json": components["schemas"]["user"] + } + } + } + delete: { + parameters: { + path: { + /** The ID of the user which this request is targeting. */ + userId: components["parameters"]["userId"] + } + } + responses: { + /** Returns the deleted user. */ + 200: { + content: { + "application/json": components["schemas"]["userOutput"] + } + } + } + } + } + "/users/search": { + /** Based on user properties (currently only name) search for users. */ + post: { + responses: { + /** Returns the found users based on search parameters. */ + 200: { + content: { + "application/json": components["schemas"]["userSearch"] + } + } + } + requestBody: { + content: { + "application/json": components["schemas"]["nameSearch"] + } + } + } + } +} + +export interface components { + schemas: { + application: { + /** @description The name of the app. */ + name: string + /** @description The URL by which the app is accessed, this must be URL encoded. */ + url: string + } + applicationOutput: { + data: { + /** @description The name of the app. */ + name: string + /** @description The URL by which the app is accessed, this must be URL encoded. */ + url: string + /** @description The ID of the app. */ + _id: string + /** + * @description The status of the app, stating it if is the development or published version. + * @enum {string} + */ + status: "development" | "published" + /** @description States when the app was created, will be constant. Stored in ISO format. */ + createdAt: string + /** @description States the last time the app was updated - stored in ISO format. */ + updatedAt: string + /** @description States the version of the Budibase client this app is currently based on. */ + version: string + /** @description In a multi-tenant environment this will state the tenant this app is within. */ + tenantId?: string + /** @description The user this app is currently being built by. */ + lockedBy?: { [key: string]: unknown } + } + } + applicationSearch: { + data: { + /** @description The name of the app. */ + name: string + /** @description The URL by which the app is accessed, this must be URL encoded. */ + url: string + /** @description The ID of the app. */ + _id: string + /** + * @description The status of the app, stating it if is the development or published version. + * @enum {string} + */ + status: "development" | "published" + /** @description States when the app was created, will be constant. Stored in ISO format. */ + createdAt: string + /** @description States the last time the app was updated - stored in ISO format. */ + updatedAt: string + /** @description States the version of the Budibase client this app is currently based on. */ + version: string + /** @description In a multi-tenant environment this will state the tenant this app is within. */ + tenantId?: string + /** @description The user this app is currently being built by. */ + lockedBy?: { [key: string]: unknown } + }[] + } + /** @description The row to be created/updated, based on the table schema. */ + row: { [key: string]: unknown } + searchOutput: { + /** @description An array of rows, these will each contain an _id field which can be used to update or delete them. */ + data: { [key: string]: unknown }[] + /** @description If pagination in use, this should be provided. */ + bookmark?: string | number + /** @description If pagination in use, this will determine if there is another page to fetch. */ + hasNextPage?: boolean + } + rowOutput: { + /** @description The row to be created/updated, based on the table schema. */ + data: { + /** @description The ID of the row. */ + _id: string + /** @description The ID of the table this row comes from. */ + tableId: string + } & { [key: string]: unknown } + } + /** @description The table to be created/updated. */ + table: { + /** @description The name of the table. */ + name: string + /** @description The name of the column which should be used in relationship tags when relating to this table. */ + primaryDisplay?: string + schema: { + [key: string]: + | { + /** + * @description A relationship column. + * @enum {string} + */ + type?: "link" + /** @description A constraint can be applied to the column which will be validated against when a row is saved. */ + constraints?: { + /** @enum {string} */ + type?: "string" | "number" | "object" | "boolean" + /** @description Defines whether the column is required or not. */ + presence?: boolean + } + /** @description The name of the column. */ + name?: string + /** @description Defines whether the column is automatically generated. */ + autocolumn?: boolean + /** @description The name of the column which a relationship column is related to in another table. */ + fieldName?: string + /** @description The ID of the table which a relationship column is related to. */ + tableId?: string + /** + * @description Defines the type of relationship that this column will be used for. + * @enum {string} + */ + relationshipType?: "one-to-many" | "many-to-one" | "many-to-many" + /** @description When using a SQL table that contains many to many relationships this defines the table the relationships are linked through. */ + through?: string + /** @description When using a SQL table that contains a one to many relationship this defines the foreign key. */ + foreignKey?: string + /** @description When using a SQL table that utilises a through table, this defines the primary key in the through table for this table. */ + throughFrom?: string + /** @description When using a SQL table that utilises a through table, this defines the primary key in the through table for the related table. */ + throughTo?: string + } + | { + /** + * @description A formula column. + * @enum {string} + */ + type?: "formula" + /** @description A constraint can be applied to the column which will be validated against when a row is saved. */ + constraints?: { + /** @enum {string} */ + type?: "string" | "number" | "object" | "boolean" + /** @description Defines whether the column is required or not. */ + presence?: boolean + } + /** @description The name of the column. */ + name?: string + /** @description Defines whether the column is automatically generated. */ + autocolumn?: boolean + /** @description Defines a Handlebars or JavaScript formula to use, note that Javascript formulas are expected to be provided in the base64 format. */ + formula?: string + /** + * @description Defines whether this is a static or dynamic formula. + * @enum {string} + */ + formulaType?: "static" | "dynamic" + } + | { + /** + * @description Defines the type of the column, most explain themselves, a link column is a relationship. + * @enum {string} + */ + type?: + | "string" + | "longform" + | "options" + | "number" + | "boolean" + | "array" + | "datetime" + | "attachment" + | "link" + | "formula" + | "auto" + | "json" + | "internal" + /** @description A constraint can be applied to the column which will be validated against when a row is saved. */ + constraints?: { + /** @enum {string} */ + type?: "string" | "number" | "object" | "boolean" + /** @description Defines whether the column is required or not. */ + presence?: boolean + } + /** @description The name of the column. */ + name?: string + /** @description Defines whether the column is automatically generated. */ + autocolumn?: boolean + } + } + } + tableOutput: { + /** @description The table to be created/updated. */ + data: { + /** @description The name of the table. */ + name: string + /** @description The name of the column which should be used in relationship tags when relating to this table. */ + primaryDisplay?: string + schema: { + [key: string]: + | { + /** + * @description A relationship column. + * @enum {string} + */ + type?: "link" + /** @description A constraint can be applied to the column which will be validated against when a row is saved. */ + constraints?: { + /** @enum {string} */ + type?: "string" | "number" | "object" | "boolean" + /** @description Defines whether the column is required or not. */ + presence?: boolean + } + /** @description The name of the column. */ + name?: string + /** @description Defines whether the column is automatically generated. */ + autocolumn?: boolean + /** @description The name of the column which a relationship column is related to in another table. */ + fieldName?: string + /** @description The ID of the table which a relationship column is related to. */ + tableId?: string + /** + * @description Defines the type of relationship that this column will be used for. + * @enum {string} + */ + relationshipType?: + | "one-to-many" + | "many-to-one" + | "many-to-many" + /** @description When using a SQL table that contains many to many relationships this defines the table the relationships are linked through. */ + through?: string + /** @description When using a SQL table that contains a one to many relationship this defines the foreign key. */ + foreignKey?: string + /** @description When using a SQL table that utilises a through table, this defines the primary key in the through table for this table. */ + throughFrom?: string + /** @description When using a SQL table that utilises a through table, this defines the primary key in the through table for the related table. */ + throughTo?: string + } + | { + /** + * @description A formula column. + * @enum {string} + */ + type?: "formula" + /** @description A constraint can be applied to the column which will be validated against when a row is saved. */ + constraints?: { + /** @enum {string} */ + type?: "string" | "number" | "object" | "boolean" + /** @description Defines whether the column is required or not. */ + presence?: boolean + } + /** @description The name of the column. */ + name?: string + /** @description Defines whether the column is automatically generated. */ + autocolumn?: boolean + /** @description Defines a Handlebars or JavaScript formula to use, note that Javascript formulas are expected to be provided in the base64 format. */ + formula?: string + /** + * @description Defines whether this is a static or dynamic formula. + * @enum {string} + */ + formulaType?: "static" | "dynamic" + } + | { + /** + * @description Defines the type of the column, most explain themselves, a link column is a relationship. + * @enum {string} + */ + type?: + | "string" + | "longform" + | "options" + | "number" + | "boolean" + | "array" + | "datetime" + | "attachment" + | "link" + | "formula" + | "auto" + | "json" + | "internal" + /** @description A constraint can be applied to the column which will be validated against when a row is saved. */ + constraints?: { + /** @enum {string} */ + type?: "string" | "number" | "object" | "boolean" + /** @description Defines whether the column is required or not. */ + presence?: boolean + } + /** @description The name of the column. */ + name?: string + /** @description Defines whether the column is automatically generated. */ + autocolumn?: boolean + } + } + /** @description The ID of the table. */ + _id: string + } + } + tableSearch: { + data: { + /** @description The name of the table. */ + name: string + /** @description The name of the column which should be used in relationship tags when relating to this table. */ + primaryDisplay?: string + schema: { + [key: string]: + | { + /** + * @description A relationship column. + * @enum {string} + */ + type?: "link" + /** @description A constraint can be applied to the column which will be validated against when a row is saved. */ + constraints?: { + /** @enum {string} */ + type?: "string" | "number" | "object" | "boolean" + /** @description Defines whether the column is required or not. */ + presence?: boolean + } + /** @description The name of the column. */ + name?: string + /** @description Defines whether the column is automatically generated. */ + autocolumn?: boolean + /** @description The name of the column which a relationship column is related to in another table. */ + fieldName?: string + /** @description The ID of the table which a relationship column is related to. */ + tableId?: string + /** + * @description Defines the type of relationship that this column will be used for. + * @enum {string} + */ + relationshipType?: + | "one-to-many" + | "many-to-one" + | "many-to-many" + /** @description When using a SQL table that contains many to many relationships this defines the table the relationships are linked through. */ + through?: string + /** @description When using a SQL table that contains a one to many relationship this defines the foreign key. */ + foreignKey?: string + /** @description When using a SQL table that utilises a through table, this defines the primary key in the through table for this table. */ + throughFrom?: string + /** @description When using a SQL table that utilises a through table, this defines the primary key in the through table for the related table. */ + throughTo?: string + } + | { + /** + * @description A formula column. + * @enum {string} + */ + type?: "formula" + /** @description A constraint can be applied to the column which will be validated against when a row is saved. */ + constraints?: { + /** @enum {string} */ + type?: "string" | "number" | "object" | "boolean" + /** @description Defines whether the column is required or not. */ + presence?: boolean + } + /** @description The name of the column. */ + name?: string + /** @description Defines whether the column is automatically generated. */ + autocolumn?: boolean + /** @description Defines a Handlebars or JavaScript formula to use, note that Javascript formulas are expected to be provided in the base64 format. */ + formula?: string + /** + * @description Defines whether this is a static or dynamic formula. + * @enum {string} + */ + formulaType?: "static" | "dynamic" + } + | { + /** + * @description Defines the type of the column, most explain themselves, a link column is a relationship. + * @enum {string} + */ + type?: + | "string" + | "longform" + | "options" + | "number" + | "boolean" + | "array" + | "datetime" + | "attachment" + | "link" + | "formula" + | "auto" + | "json" + | "internal" + /** @description A constraint can be applied to the column which will be validated against when a row is saved. */ + constraints?: { + /** @enum {string} */ + type?: "string" | "number" | "object" | "boolean" + /** @description Defines whether the column is required or not. */ + presence?: boolean + } + /** @description The name of the column. */ + name?: string + /** @description Defines whether the column is automatically generated. */ + autocolumn?: boolean + } + } + /** @description The ID of the table. */ + _id: string + }[] + } + /** @description The query body must contain the required parameters for the query, this depends on query type, setup and bindings. */ + executeQuery: { [key: string]: unknown } + executeQueryOutput: { + /** @description The data response from the query. */ + data: { [key: string]: unknown }[] + /** @description Extra information that is not part of the main data, e.g. headers. */ + extra?: { + /** @description If carrying out a REST request, this will contain the response headers. */ + headers?: { [key: string]: unknown } + /** @description The raw query response, as a string. */ + raw?: string + } + /** @description If pagination is supported, this will contain the bookmark/anchor information for it. */ + pagination?: { [key: string]: unknown } + } + query: { + /** @description The ID of the query. */ + _id: string + /** @description The ID of the data source the query belongs to. */ + datasourceId?: string + /** @description The bindings which are required to perform this query. */ + parameters?: string[] + /** @description The fields that are used to perform this query, e.g. the sql statement */ + fields?: { [key: string]: unknown } + /** + * @description The verb that describes this query. + * @enum {undefined} + */ + queryVerb?: "create" | "read" | "update" | "delete" + /** @description The name of the query. */ + name: string + /** @description The schema of the data returned when the query is executed. */ + schema: { [key: string]: unknown } + /** @description The JavaScript transformer function, applied after the query responds with data. */ + transformer?: string + /** @description Whether the query has readable data. */ + readable?: boolean + } + querySearch: { + data: { + /** @description The ID of the query. */ + _id: string + /** @description The ID of the data source the query belongs to. */ + datasourceId?: string + /** @description The bindings which are required to perform this query. */ + parameters?: string[] + /** @description The fields that are used to perform this query, e.g. the sql statement */ + fields?: { [key: string]: unknown } + /** + * @description The verb that describes this query. + * @enum {undefined} + */ + queryVerb?: "create" | "read" | "update" | "delete" + /** @description The name of the query. */ + name: string + /** @description The schema of the data returned when the query is executed. */ + schema: { [key: string]: unknown } + /** @description The JavaScript transformer function, applied after the query responds with data. */ + transformer?: string + /** @description Whether the query has readable data. */ + readable?: boolean + }[] + } + user: { + /** @description The email address of the user, this must be unique. */ + email: string + /** @description The password of the user if using password based login - this will never be returned. This can be left out of subsequent requests (updates) and will be enriched back into the user structure. */ + password?: string + /** + * @description The status of the user, if they are active. + * @enum {string} + */ + status?: "active" + /** @description The first name of the user */ + firstName?: string + /** @description The last name of the user */ + lastName?: string + /** @description If set to true forces the user to reset their password on first login. */ + forceResetPassword?: boolean + /** @description Describes if the user is a builder user or not. */ + builder?: { + /** @description If set to true the user will be able to build any app in the system. */ + global?: boolean + } + /** @description Describes if the user is an admin user or not. */ + admin?: { + /** @description If set to true the user will be able to administrate the system. */ + global?: boolean + } + /** @description Contains the roles of the user per app (assuming they are not a builder user). */ + roles: { [key: string]: string } + } + userOutput: { + data: { + /** @description The email address of the user, this must be unique. */ + email: string + /** @description The password of the user if using password based login - this will never be returned. This can be left out of subsequent requests (updates) and will be enriched back into the user structure. */ + password?: string + /** + * @description The status of the user, if they are active. + * @enum {string} + */ + status?: "active" + /** @description The first name of the user */ + firstName?: string + /** @description The last name of the user */ + lastName?: string + /** @description If set to true forces the user to reset their password on first login. */ + forceResetPassword?: boolean + /** @description Describes if the user is a builder user or not. */ + builder?: { + /** @description If set to true the user will be able to build any app in the system. */ + global?: boolean + } + /** @description Describes if the user is an admin user or not. */ + admin?: { + /** @description If set to true the user will be able to administrate the system. */ + global?: boolean + } + /** @description Contains the roles of the user per app (assuming they are not a builder user). */ + roles: { [key: string]: string } + /** @description The ID of the user. */ + _id: string + } + } + userSearch: { + data: { + /** @description The email address of the user, this must be unique. */ + email: string + /** @description The password of the user if using password based login - this will never be returned. This can be left out of subsequent requests (updates) and will be enriched back into the user structure. */ + password?: string + /** + * @description The status of the user, if they are active. + * @enum {string} + */ + status?: "active" + /** @description The first name of the user */ + firstName?: string + /** @description The last name of the user */ + lastName?: string + /** @description If set to true forces the user to reset their password on first login. */ + forceResetPassword?: boolean + /** @description Describes if the user is a builder user or not. */ + builder?: { + /** @description If set to true the user will be able to build any app in the system. */ + global?: boolean + } + /** @description Describes if the user is an admin user or not. */ + admin?: { + /** @description If set to true the user will be able to administrate the system. */ + global?: boolean + } + /** @description Contains the roles of the user per app (assuming they are not a builder user). */ + roles: { [key: string]: string } + /** @description The ID of the user. */ + _id: string + }[] + } + nameSearch: { + /** @description The name to be used when searching - this will be used in a case insensitive starts with match. */ + name: string + } + } + parameters: { + /** @description The ID of the table which this request is targeting. */ + tableId: string + /** @description The ID of the row which this request is targeting. */ + rowId: string + /** @description The ID of the app which this request is targeting. */ + appId: string + /** @description The ID of the app which this request is targeting. */ + appIdUrl: string + /** @description The ID of the query which this request is targeting. */ + queryId: string + /** @description The ID of the user which this request is targeting. */ + userId: string + } +} + +export interface operations {} + +export interface external {} diff --git a/examples/nextjs-api-sales/next-env.d.ts b/examples/nextjs-api-sales/next-env.d.ts new file mode 100644 index 0000000000..4f11a03dc6 --- /dev/null +++ b/examples/nextjs-api-sales/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/examples/nextjs-api-sales/next.config.js b/examples/nextjs-api-sales/next.config.js new file mode 100644 index 0000000000..cfdafdaf5a --- /dev/null +++ b/examples/nextjs-api-sales/next.config.js @@ -0,0 +1,16 @@ +const { join } = require("path") +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, + sassOptions: { + includePaths: [join(__dirname, "styles")], + }, + serverRuntimeConfig: { + apiKey: + "bf4d86af933b5ac0af0fdbe4bf7d89ff-f929752a1eeaafb00f4b5e3325097d51a44fe4b39f22ed857923409cc75414b379323a25ebfb4916", + appName: "sales", + host: "http://localhost:10000", + }, +} + +module.exports = nextConfig diff --git a/examples/nextjs-api-sales/package.json b/examples/nextjs-api-sales/package.json new file mode 100644 index 0000000000..6d75c85f01 --- /dev/null +++ b/examples/nextjs-api-sales/package.json @@ -0,0 +1,27 @@ +{ + "name": "nextjs-api-sales", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev -p 3001", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "bulma": "^0.9.3", + "next": "12.1.0", + "node-fetch": "^3.2.2", + "node-sass": "^7.0.1", + "react": "17.0.2", + "react-dom": "17.0.2", + "react-notifications-component": "^3.4.1" + }, + "devDependencies": { + "@types/node": "17.0.21", + "@types/react": "17.0.39", + "eslint": "8.10.0", + "eslint-config-next": "12.1.0", + "typescript": "4.6.2" + } +} diff --git a/examples/nextjs-api-sales/pages/_app.tsx b/examples/nextjs-api-sales/pages/_app.tsx new file mode 100644 index 0000000000..0669422fa4 --- /dev/null +++ b/examples/nextjs-api-sales/pages/_app.tsx @@ -0,0 +1,17 @@ +import "../styles/global.sass" +import type { AppProps } from "next/app" +import Head from "next/head" +import Layout from "../components/layout" + +function MyApp({ Component, pageProps }: AppProps) { + return ( + + + BB NextJS Sales Example + + + + ) +} + +export default MyApp diff --git a/examples/nextjs-api-sales/pages/api/sales.ts b/examples/nextjs-api-sales/pages/api/sales.ts new file mode 100644 index 0000000000..1652402158 --- /dev/null +++ b/examples/nextjs-api-sales/pages/api/sales.ts @@ -0,0 +1,46 @@ +import { getApp, findTable, makeCall } from "../../utilities" + +async function getSales(req: any) { + const { page } = req.query + const { _id: appId } = await getApp() + const table = await findTable(appId, "sales") + return await makeCall("post", `tables/${table._id}/rows/search`, { + appId, + body: { + limit: 10, + sort: { + type: "string", + order: "descending", + column: "sale_id", + }, + paginate: true, + bookmark: parseInt(page), + }, + }) +} + +async function saveSale(req: any) { + const { _id: appId } = await getApp() + const table = await findTable(appId, "sales") + return await makeCall("post", `tables/${table._id}/rows`, { + body: req.body, + appId, + }) +} + +export default async function handler(req: any, res: any) { + let response: any = {} + try { + if (req.method === "POST") { + response = await saveSale(req) + } else if (req.method === "GET") { + response = await getSales(req) + } else { + res.status(404) + return + } + res.status(200).json(response) + } catch (err: any) { + res.status(400).send(err) + } +} diff --git a/examples/nextjs-api-sales/pages/api/salespeople.ts b/examples/nextjs-api-sales/pages/api/salespeople.ts new file mode 100644 index 0000000000..8ee43ab7c8 --- /dev/null +++ b/examples/nextjs-api-sales/pages/api/salespeople.ts @@ -0,0 +1,31 @@ +import { getApp, findTable, makeCall } from "../../utilities" + +async function getSalespeople() { + const { _id: appId } = await getApp() + const table = await findTable(appId, "sales_people") + return await makeCall("post", `tables/${table._id}/rows/search`, { + appId, + body: { + sort: { + type: "string", + order: "ascending", + column: "person_id", + }, + }, + }) +} + +export default async function handler(req: any, res: any) { + let response: any = {} + try { + if (req.method === "GET") { + response = await getSalespeople() + } else { + res.status(404) + return + } + res.status(200).json(response) + } catch (err: any) { + res.status(400).send(err) + } +} diff --git a/examples/nextjs-api-sales/pages/index.tsx b/examples/nextjs-api-sales/pages/index.tsx new file mode 100644 index 0000000000..eae1909ff4 --- /dev/null +++ b/examples/nextjs-api-sales/pages/index.tsx @@ -0,0 +1,83 @@ +import type { NextPage } from "next" +import styles from "../styles/home.module.css" +import { useState, useEffect, useCallback } from "react" +import Notifications from "../components/notifications" + +const Home: NextPage = () => { + const [sales, setSales] = useState([]) + const [currentPage, setCurrentPage] = useState(1) + const [loaded, setLoaded] = useState(false) + + const getSales = useCallback(async (page: Number = 1) => { + let url = "/api/sales" + if (page) { + url += `?page=${page}` + } + const response = await fetch(url) + if (!response.ok) { + const error = await response.text() + Notifications.error(error, "Failed to get sales") + return + } + const sales = await response.json() + // @ts-ignore + setCurrentPage(page) + return setSales(sales.data) + }, []) + + const goToNextPage = useCallback(async () => { + await getSales(currentPage + 1) + }, [currentPage, getSales]) + + const goToPrevPage = useCallback(async () => { + if (currentPage > 1) { + await getSales(currentPage - 1) + } + }, [currentPage, getSales]) + + useEffect(() => { + getSales().then(() => { + setLoaded(true) + }).catch(() => { + setSales([]) + }) + }, []) + + if (!loaded) { + return null + } + + return ( +
+
+

Sales

+
+ + + + + + + + + + {sales.map((sale: any) => + + + + + + )} + +
Sale IDnameSold by
{sale.sale_id}{sale.sale_name}{sale.sales_person?.map((person: any) => person.primaryDisplay)[0]}
+
+ + +
+
+
+
+ ) +} + +export default Home diff --git a/examples/nextjs-api-sales/pages/save.tsx b/examples/nextjs-api-sales/pages/save.tsx new file mode 100644 index 0000000000..5b1ab5abff --- /dev/null +++ b/examples/nextjs-api-sales/pages/save.tsx @@ -0,0 +1,81 @@ +import type { NextPage } from "next" +import { useCallback, useEffect, useState } from "react" +import styles from "../styles/save.module.css" +import Notifications from "../components/notifications" + +const Save: NextPage = () => { + const [salespeople, setSalespeople] = useState([]) + const [loaded, setLoaded] = useState(false) + + const saveSale = useCallback(async (event: any) => { + event.preventDefault() + const sale = { + sale_name: event.target.name.value, + sales_person: [event.target.soldBy.value], + } + const response = await fetch("/api/sales", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(sale), + }) + if (!response.ok) { + const error = await response.text() + Notifications.error(error, "Failed to save sale") + return + } + Notifications.success("Sale saved successfully!", "Sale saved") + }, []) + + const getSalespeople = useCallback(async () => { + const response: any = await fetch("/api/salespeople") + if (!response.ok) { + throw new Error(await response.text()) + } + const json = await response.json() + setSalespeople(json.data) + }, []) + + useEffect(() => { + getSalespeople().then(() => { + setLoaded(true) + }).catch(() => { + setSalespeople([]) + }) + }, []) + + if (!loaded) { + return null + } + return ( +
+
+

New sale

+
+
+ +
+ +
+
+
+ +
+
+ +
+
+
+
+ +
+
+
+
+ ) +} + +export default Save \ No newline at end of file diff --git a/examples/nextjs-api-sales/public/bb-emblem.svg b/examples/nextjs-api-sales/public/bb-emblem.svg new file mode 100644 index 0000000000..9f4f3690d5 --- /dev/null +++ b/examples/nextjs-api-sales/public/bb-emblem.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/nextjs-api-sales/public/favicon.ico b/examples/nextjs-api-sales/public/favicon.ico new file mode 100644 index 0000000000..718d6fea48 Binary files /dev/null and b/examples/nextjs-api-sales/public/favicon.ico differ diff --git a/examples/nextjs-api-sales/styles/global.sass b/examples/nextjs-api-sales/styles/global.sass new file mode 100644 index 0000000000..b26af79b39 --- /dev/null +++ b/examples/nextjs-api-sales/styles/global.sass @@ -0,0 +1,26 @@ +@charset "utf-8" + +@import url('https://fonts.googleapis.com/css?family=Roboto:400,700') +$family-sans-serif: "Roboto", sans-serif + +#__next + display: flex + flex-direction: column + justify-content: flex-start + align-items: stretch + height: 100vh + --bg-color: #f5f5f5 + +.logo + padding: 0.75rem + +@import "../node_modules/bulma/bulma.sass" +@import "../node_modules/react-notifications-component/dist/theme.css" + +// applied after bulma styles are enabled +html + overflow-y: auto + +.navbar + background-color: var(--bg-color) + color: white \ No newline at end of file diff --git a/examples/nextjs-api-sales/styles/home.module.css b/examples/nextjs-api-sales/styles/home.module.css new file mode 100644 index 0000000000..23e0fc8bf9 --- /dev/null +++ b/examples/nextjs-api-sales/styles/home.module.css @@ -0,0 +1,30 @@ +.container { + width: 100vw; + display: flex; + flex-direction: column; + padding: 5rem 2rem 0; + align-items: center; + flex: 1 1 auto; +} + +.buttons { + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.tableSection { + padding: 2rem; + background: var(--bg-color); + width: 800px; + border-radius: 10px; +} + +.table table { + width: 100%; +} + +.tableSection h1 { + text-align: center; + color: black; +} \ No newline at end of file diff --git a/examples/nextjs-api-sales/styles/save.module.css b/examples/nextjs-api-sales/styles/save.module.css new file mode 100644 index 0000000000..9c39943008 --- /dev/null +++ b/examples/nextjs-api-sales/styles/save.module.css @@ -0,0 +1,20 @@ +.container { + width: 100vw; + display: flex; + flex-direction: column; + padding: 5rem 2rem 0; + align-items: center; + flex: 1 1 auto; +} + +.formSection { + padding: 2rem; + background: var(--bg-color); + width: 400px; + border-radius: 10px; +} + +.formSection h1 { + text-align: center; + color: black; +} \ No newline at end of file diff --git a/examples/nextjs-api-sales/tsconfig.json b/examples/nextjs-api-sales/tsconfig.json new file mode 100644 index 0000000000..99710e8578 --- /dev/null +++ b/examples/nextjs-api-sales/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +} diff --git a/examples/nextjs-api-sales/utilities/index.ts b/examples/nextjs-api-sales/utilities/index.ts new file mode 100644 index 0000000000..0312e6e434 --- /dev/null +++ b/examples/nextjs-api-sales/utilities/index.ts @@ -0,0 +1,82 @@ +import { App, AppSearch, Table, TableSearch } from "../definitions" +import getConfig from "next/config" + +const { serverRuntimeConfig } = getConfig() +const apiKey = serverRuntimeConfig["apiKey"] +const appName = serverRuntimeConfig["appName"] +const host = serverRuntimeConfig["host"] + +let APP: App | null = null +let TABLES: { [key: string]: Table } = {} + +export async function makeCall( + method: string, + url: string, + opts?: { body?: any; appId?: string } +): Promise { + const fetchOpts: any = { + method, + headers: { + "x-budibase-api-key": apiKey, + }, + } + if (opts?.appId) { + fetchOpts.headers["x-budibase-app-id"] = opts.appId + } + if (opts?.body) { + fetchOpts.body = + typeof opts.body !== "string" ? JSON.stringify(opts.body) : opts.body + fetchOpts.headers["Content-Type"] = "application/json" + } + const finalUrl = `${host}/api/public/v1/${url}` + const response = await fetch(finalUrl, fetchOpts) + if (response.ok) { + return response.json() + } else { + const error = await response.text() + console.error("Budibase server error - ", error) + throw new Error(error) + } +} + +export async function getApp(): Promise { + if (APP) { + return APP + } + const apps: AppSearch = await makeCall("post", "applications/search", { + body: { + name: appName, + }, + }) + const app = apps.data.find((app: App) => app.name === appName) + if (!app) { + throw new Error( + "Could not find app, please make sure app name in config is correct." + ) + } + APP = app + return app +} + +export async function findTable( + appId: string, + tableName: string +): Promise { + if (TABLES[tableName]) { + return TABLES[tableName] + } + const tables: TableSearch = await makeCall("post", "tables/search", { + body: { + name: tableName, + }, + appId, + }) + const table = tables.data.find((table: Table) => table.name === tableName) + if (!table) { + throw new Error( + "Could not find table, please make sure your app is configured with the Postgres datasource correctly." + ) + } + TABLES[tableName] = table + return table +} diff --git a/examples/nextjs-api-sales/yarn.lock b/examples/nextjs-api-sales/yarn.lock new file mode 100644 index 0000000000..3f32417ba8 --- /dev/null +++ b/examples/nextjs-api-sales/yarn.lock @@ -0,0 +1,3086 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# 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" + integrity sha512-NcKtr2epxfIrNM4VOmPKO46TvDMCBhgi2CrSHaEarrz+Plk2K5r9QemmOFTGpZaoKnWoGH5MO+CzeRsih/Fcgg== + dependencies: + core-js-pure "^3.20.2" + regenerator-runtime "^0.13.4" + +"@babel/runtime@^7.10.2", "@babel/runtime@^7.16.3": + version "7.17.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.2.tgz#66f68591605e59da47523c631416b18508779941" + integrity sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw== + dependencies: + regenerator-runtime "^0.13.4" + +"@eslint/eslintrc@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.0.tgz#7ce1547a5c46dfe56e1e45c3c9ed18038c721c6a" + integrity sha512-igm9SjJHNEJRiUnecP/1R5T3wKLEJ7pL6e2P+GUSfCd0dGjPYYZve08uzw8L2J8foVHFz+NGu12JxRcU2gGo6w== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.3.1" + globals "^13.9.0" + ignore "^4.0.6" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + 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" + integrity sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw== + dependencies: + "@humanwhocodes/object-schema" "^1.2.1" + debug "^4.1.1" + minimatch "^3.0.4" + +"@humanwhocodes/object-schema@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + +"@next/env@12.1.0": + version "12.1.0" + resolved "https://registry.yarnpkg.com/@next/env/-/env-12.1.0.tgz#73713399399b34aa5a01771fb73272b55b22c314" + integrity sha512-nrIgY6t17FQ9xxwH3jj0a6EOiQ/WDHUos35Hghtr+SWN/ntHIQ7UpuvSi0vaLzZVHQWaDupKI+liO5vANcDeTQ== + +"@next/eslint-plugin-next@12.1.0": + version "12.1.0" + resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-12.1.0.tgz#32586a11378b3ffa5a93ac40a3c44ad99d70e95a" + integrity sha512-WFiyvSM2G5cQmh32t/SiQuJ+I2O+FHVlK/RFw5b1565O2kEM/36EXncjt88Pa+X5oSc+1SS+tWxowWJd1lqI+g== + dependencies: + glob "7.1.7" + +"@next/swc-android-arm64@12.1.0": + version "12.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-android-arm64/-/swc-android-arm64-12.1.0.tgz#865ba3a9afc204ff2bdeea49dd64d58705007a39" + integrity sha512-/280MLdZe0W03stA69iL+v6I+J1ascrQ6FrXBlXGCsGzrfMaGr7fskMa0T5AhQIVQD4nA/46QQWxG//DYuFBcA== + +"@next/swc-darwin-arm64@12.1.0": + version "12.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.1.0.tgz#08e8b411b8accd095009ed12efbc2f1d4d547135" + integrity sha512-R8vcXE2/iONJ1Unf5Ptqjk6LRW3bggH+8drNkkzH4FLEQkHtELhvcmJwkXcuipyQCsIakldAXhRbZmm3YN1vXg== + +"@next/swc-darwin-x64@12.1.0": + version "12.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-12.1.0.tgz#fcd684497a76e8feaca88db3c394480ff0b007cd" + integrity sha512-ieAz0/J0PhmbZBB8+EA/JGdhRHBogF8BWaeqR7hwveb6SYEIJaDNQy0I+ZN8gF8hLj63bEDxJAs/cEhdnTq+ug== + +"@next/swc-linux-arm-gnueabihf@12.1.0": + version "12.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.1.0.tgz#9ec6380a27938a5799aaa6035c205b3c478468a7" + integrity sha512-njUd9hpl6o6A5d08dC0cKAgXKCzm5fFtgGe6i0eko8IAdtAPbtHxtpre3VeSxdZvuGFh+hb0REySQP9T1ttkog== + +"@next/swc-linux-arm64-gnu@12.1.0": + version "12.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.1.0.tgz#7f4196dff1049cea479607c75b81033ae2dbd093" + integrity sha512-OqangJLkRxVxMhDtcb7Qn1xjzFA3s50EIxY7mljbSCLybU+sByPaWAHY4px97ieOlr2y4S0xdPKkQ3BCAwyo6Q== + +"@next/swc-linux-arm64-musl@12.1.0": + version "12.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.1.0.tgz#b445f767569cdc2dddee785ca495e1a88c025566" + integrity sha512-hB8cLSt4GdmOpcwRe2UzI5UWn6HHO/vLkr5OTuNvCJ5xGDwpPXelVkYW/0+C3g5axbDW2Tym4S+MQCkkH9QfWA== + +"@next/swc-linux-x64-gnu@12.1.0": + version "12.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.1.0.tgz#67610e9be4fbc987de7535f1bcb17e45fe12f90e" + integrity sha512-OKO4R/digvrVuweSw/uBM4nSdyzsBV5EwkUeeG4KVpkIZEe64ZwRpnFB65bC6hGwxIBnTv5NMSnJ+0K/WmG78A== + +"@next/swc-linux-x64-musl@12.1.0": + version "12.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.1.0.tgz#ea19a23db08a9f2e34ac30401f774cf7d1669d31" + integrity sha512-JohhgAHZvOD3rQY7tlp7NlmvtvYHBYgY0x5ZCecUT6eCCcl9lv6iV3nfu82ErkxNk1H893fqH0FUpznZ/H3pSw== + +"@next/swc-win32-arm64-msvc@12.1.0": + version "12.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.1.0.tgz#eadf054fc412085659b98e145435bbba200b5283" + integrity sha512-T/3gIE6QEfKIJ4dmJk75v9hhNiYZhQYAoYm4iVo1TgcsuaKLFa+zMPh4056AHiG6n9tn2UQ1CFE8EoybEsqsSw== + +"@next/swc-win32-ia32-msvc@12.1.0": + version "12.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.1.0.tgz#68faeae10c89f698bf9d28759172b74c9c21bda1" + integrity sha512-iwnKgHJdqhIW19H9PRPM9j55V6RdcOo6rX+5imx832BCWzkDbyomWnlzBfr6ByUYfhohb8QuH4hSGEikpPqI0Q== + +"@next/swc-win32-x64-msvc@12.1.0": + version "12.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.1.0.tgz#d27e7e76c87a460a4da99c5bfdb1618dcd6cd064" + integrity sha512-aBvcbMwuanDH4EMrL2TthNJy+4nP59Bimn8egqv6GHMVj0a44cU6Au4PjOhLNqEh9l+IpRGBqMTzec94UdC5xg== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@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" + integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== + +"@types/react@17.0.39": + version "17.0.39" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.39.tgz#d0f4cde092502a6db00a1cded6e6bf2abb7633ce" + integrity sha512-UVavlfAxDd/AgAacMa60Azl7ygyQNRwC/DsHZmKgNvPmRR5p70AJ5Q9EAmL2NWOJmeV+vVUI4IAP7GZrN8h8Ug== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + +"@types/scheduler@*": + version "0.16.2" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" + integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== + +"@typescript-eslint/parser@^5.0.0": + version "5.14.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.14.0.tgz#7c79f898aa3cff0ceee6f1d34eeed0f034fb9ef3" + integrity sha512-aHJN8/FuIy1Zvqk4U/gcO/fxeMKyoSv/rS46UXMXOJKVsLQ+iYPuXNbpbH7cBLcpSbmyyFbwrniLx5+kutu1pw== + dependencies: + "@typescript-eslint/scope-manager" "5.14.0" + "@typescript-eslint/types" "5.14.0" + "@typescript-eslint/typescript-estree" "5.14.0" + debug "^4.3.2" + +"@typescript-eslint/scope-manager@5.14.0": + version "5.14.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.14.0.tgz#ea518962b42db8ed0a55152ea959c218cb53ca7b" + integrity sha512-LazdcMlGnv+xUc5R4qIlqH0OWARyl2kaP8pVCS39qSL3Pd1F7mI10DbdXeARcE62sVQE4fHNvEqMWsypWO+yEw== + dependencies: + "@typescript-eslint/types" "5.14.0" + "@typescript-eslint/visitor-keys" "5.14.0" + +"@typescript-eslint/types@5.14.0": + version "5.14.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.14.0.tgz#96317cf116cea4befabc0defef371a1013f8ab11" + integrity sha512-BR6Y9eE9360LNnW3eEUqAg6HxS9Q35kSIs4rp4vNHRdfg0s+/PgHgskvu5DFTM7G5VKAVjuyaN476LCPrdA7Mw== + +"@typescript-eslint/typescript-estree@5.14.0": + version "5.14.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.14.0.tgz#78b7f7385d5b6f2748aacea5c9b7f6ae62058314" + integrity sha512-QGnxvROrCVtLQ1724GLTHBTR0lZVu13izOp9njRvMkCBgWX26PKvmMP8k82nmXBRD3DQcFFq2oj3cKDwr0FaUA== + dependencies: + "@typescript-eslint/types" "5.14.0" + "@typescript-eslint/visitor-keys" "5.14.0" + debug "^4.3.2" + globby "^11.0.4" + is-glob "^4.0.3" + semver "^7.3.5" + tsutils "^3.21.0" + +"@typescript-eslint/visitor-keys@5.14.0": + version "5.14.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.14.0.tgz#1927005b3434ccd0d3ae1b2ecf60e65943c36986" + integrity sha512-yL0XxfzR94UEkjBqyymMLgCBdojzEuy/eim7N9/RIcTNxpJudAcqsU8eRyfzBbcEzGoPWfdM3AGak3cN08WOIw== + dependencies: + "@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" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.7.0: + version "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: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +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: + 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== + 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" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +aria-query@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b" + integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA== + dependencies: + "@babel/runtime" "^7.10.2" + "@babel/runtime-corejs3" "^7.10.2" + +array-includes@^3.1.3, array-includes@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.4.tgz#f5b493162c760f3539631f005ba2bb46acb45ba9" + integrity sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + get-intrinsic "^1.1.1" + is-string "^1.0.7" + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +array.prototype.flat@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz#07e0975d84bbc7c48cd1879d609e682598d33e13" + integrity sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.0" + +array.prototype.flatmap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.5.tgz#908dc82d8a406930fdf38598d51e7411d18d4446" + integrity sha512-08u6rVyi1Lj7oqWbS9nUxliETrtIROT4XGTA4D/LWGten6E3ocm7cy9SIrmNHOL5XVbVuckUp3X6Xyg8/zpvHA== + dependencies: + call-bind "^1.0.0" + 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" + integrity sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw== + +axobject-query@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" + integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA== + +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== + +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" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +bulma@^0.9.3: + version "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" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + 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: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + 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== + 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" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + 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: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +csstype@^3.0.2: + version "3.0.11" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.11.tgz#d66700c5eacfac1940deb4e3ee5642792d85cd33" + integrity sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw== + +damerau-levenshtein@^1.0.7: + version "1.0.8" + 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" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +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= + 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= + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + 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" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + 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" + integrity sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w== + dependencies: + call-bind "^1.0.2" + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + get-intrinsic "^1.1.1" + get-symbol-description "^1.0.0" + has "^1.0.3" + has-symbols "^1.0.2" + internal-slot "^1.0.3" + is-callable "^1.2.4" + is-negative-zero "^2.0.1" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.1" + is-string "^1.0.7" + is-weakref "^1.0.1" + object-inspect "^1.11.0" + object-keys "^1.1.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.4" + string.prototype.trimstart "^1.0.4" + unbox-primitive "^1.0.1" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + 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" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-config-next@12.1.0: + version "12.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-12.1.0.tgz#8ace680dc5207e6ab6c915f3989adec122f582e7" + integrity sha512-tBhuUgoDITcdcM7xFvensi9I5WTI4dnvH4ETGRg1U8ZKpXrZsWQFdOKIDzR3RLP5HR3xXrLviaMM4c3zVoE/pA== + dependencies: + "@next/eslint-plugin-next" "12.1.0" + "@rushstack/eslint-patch" "^1.0.8" + "@typescript-eslint/parser" "^5.0.0" + eslint-import-resolver-node "^0.3.4" + eslint-import-resolver-typescript "^2.4.0" + eslint-plugin-import "^2.25.2" + eslint-plugin-jsx-a11y "^6.5.1" + eslint-plugin-react "^7.27.0" + eslint-plugin-react-hooks "^4.3.0" + +eslint-import-resolver-node@^0.3.4, eslint-import-resolver-node@^0.3.6: + version "0.3.6" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd" + integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw== + dependencies: + debug "^3.2.7" + resolve "^1.20.0" + +eslint-import-resolver-typescript@^2.4.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.5.0.tgz#07661966b272d14ba97f597b51e1a588f9722f0a" + integrity sha512-qZ6e5CFr+I7K4VVhQu3M/9xGv9/YmwsEXrsm3nimw8vWaVHRDrQRp26BgCypTxBp3vUp4o5aVEJRiy0F2DFddQ== + dependencies: + debug "^4.3.1" + glob "^7.1.7" + is-glob "^4.0.1" + resolve "^1.20.0" + tsconfig-paths "^3.9.0" + +eslint-module-utils@^2.7.2: + version "2.7.3" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz#ad7e3a10552fdd0642e1e55292781bd6e34876ee" + integrity sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ== + dependencies: + debug "^3.2.7" + find-up "^2.1.0" + +eslint-plugin-import@^2.25.2: + version "2.25.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz#322f3f916a4e9e991ac7af32032c25ce313209f1" + integrity sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA== + dependencies: + array-includes "^3.1.4" + array.prototype.flat "^1.2.5" + debug "^2.6.9" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.6" + eslint-module-utils "^2.7.2" + has "^1.0.3" + is-core-module "^2.8.0" + is-glob "^4.0.3" + minimatch "^3.0.4" + object.values "^1.1.5" + resolve "^1.20.0" + tsconfig-paths "^3.12.0" + +eslint-plugin-jsx-a11y@^6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.5.1.tgz#cdbf2df901040ca140b6ec14715c988889c2a6d8" + integrity sha512-sVCFKX9fllURnXT2JwLN5Qgo24Ug5NF6dxhkmxsMEUZhXRcGg+X3e1JbJ84YePQKBl5E0ZjAH5Q4rkdcGY99+g== + dependencies: + "@babel/runtime" "^7.16.3" + aria-query "^4.2.2" + array-includes "^3.1.4" + ast-types-flow "^0.0.7" + axe-core "^4.3.5" + axobject-query "^2.2.0" + damerau-levenshtein "^1.0.7" + emoji-regex "^9.2.2" + has "^1.0.3" + jsx-ast-utils "^3.2.1" + language-tags "^1.0.5" + minimatch "^3.0.4" + +eslint-plugin-react-hooks@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.3.0.tgz#318dbf312e06fab1c835a4abef00121751ac1172" + integrity sha512-XslZy0LnMn+84NEG9jSGR6eGqaZB3133L8xewQo3fQagbQuGt7a63gf+P1NGKZavEYEC3UXaWEAA/AqDkuN6xA== + +eslint-plugin-react@^7.27.0: + version "7.29.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.29.3.tgz#f4eab757f2756d25d6d4c2a58a9e20b004791f05" + integrity sha512-MzW6TuCnDOcta67CkpDyRfRsEVx9FNMDV8wZsDqe1luHPdGTrQIUaUXD27Ja3gHsdOIs/cXzNchWGlqm+qRVRg== + dependencies: + array-includes "^3.1.4" + array.prototype.flatmap "^1.2.5" + doctrine "^2.1.0" + estraverse "^5.3.0" + jsx-ast-utils "^2.4.1 || ^3.0.0" + minimatch "^3.1.2" + object.entries "^1.1.5" + object.fromentries "^2.0.5" + object.hasown "^1.1.0" + object.values "^1.1.5" + prop-types "^15.8.1" + resolve "^2.0.0-next.3" + semver "^6.3.0" + string.prototype.matchall "^4.0.6" + +eslint-scope@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" + integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" + +eslint-visitor-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + +eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" + integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== + +eslint@8.10.0: + version "8.10.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.10.0.tgz#931be395eb60f900c01658b278e05b6dae47199d" + integrity sha512-tcI1D9lfVec+R4LE1mNDnzoJ/f71Kl/9Cv4nG47jOueCMBrCCKYXr4AUVS7go6mWYGFD4+EoN6+eXSrEbRzXVw== + dependencies: + "@eslint/eslintrc" "^1.2.0" + "@humanwhocodes/config-array" "^0.9.2" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.1.1" + eslint-utils "^3.0.0" + eslint-visitor-keys "^3.3.0" + espree "^9.3.1" + esquery "^1.4.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^6.0.1" + globals "^13.6.0" + ignore "^5.2.0" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.0.4" + natural-compare "^1.4.0" + optionator "^0.9.1" + regexpp "^3.2.0" + strip-ansi "^6.0.1" + strip-json-comments "^3.1.0" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^9.3.1: + version "9.3.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.1.tgz#8793b4bc27ea4c778c19908e0719e7b8f4115bcd" + integrity sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ== + dependencies: + acorn "^8.7.0" + acorn-jsx "^5.3.1" + eslint-visitor-keys "^3.3.0" + +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +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" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.2.9: + version "3.2.11" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" + integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +fastq@^1.6.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" + integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== + dependencies: + reusify "^1.0.4" + +fetch-blob@^3.1.2, fetch-blob@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.1.4.tgz#e8c6567f80ad7fc22fd302e7dcb72bafde9c1717" + integrity sha512-Eq5Xv5+VlSrYWEqKrusxY1C3Hm/hjeAsCGVG3ft7pZahlUAChpGZT/Ms1WmSLnEAisEXszjzu/s+ce6HZB2VHA== + dependencies: + node-domexception "^1.0.0" + web-streams-polyfill "^3.0.3" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= + 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" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + dependencies: + flatted "^3.1.0" + rimraf "^3.0.2" + +flatted@^3.1.0: + version "3.2.5" + 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" + integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== + 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= + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +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" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^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" + integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== + dependencies: + 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: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.1: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@7.1.7, glob@~7.1.1: + version "7.1.7" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + 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: + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^13.6.0, globals@^13.9.0: + version "13.12.1" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.12.1.tgz#ec206be932e6c77236677127577aa8e50bf1c5cb" + integrity sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw== + dependencies: + type-fest "^0.20.2" + +globby@^11.0.4: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + 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" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.1, has-symbols@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + 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" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + 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" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +ignore@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== + +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" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "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" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3, 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== + +internal-slot@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" + integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== + dependencies: + get-intrinsic "^1.1.0" + 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" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + +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" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-callable@^1.1.4, is-callable@^1.2.4: + version "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: + 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== + dependencies: + has "^1.0.3" + +is-date-object@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + +is-extglob@^2.1.1: + version "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: + 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" + integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== + +is-number-object@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0" + integrity sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g== + dependencies: + has-tostringtag "^1.0.0" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-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" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-shared-array-buffer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz#97b0c85fbdacb59c9c446fe653b82cf2b5b7cfe6" + integrity sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA== + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + 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" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + 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: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + 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== + 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" + integrity sha512-uP5vu8xfy2F9A6LGC22KO7e2/vGTS1MhP+18f++ZNlf0Ohaxbc9nIEwHAsejlJKyzfZzU5UIhe5ItYkitcZnZA== + dependencies: + 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" + integrity sha512-L0IqwlIXjilBVVYKFT37X9Ih11Um5NEl9cbJIuU/SwP/zEEAbBPOnEeeuxVMf45ydWQRDQN3Nqc96OgbH1K+Pg== + +language-tags@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.5.tgz#d321dbc4da30ba8bf3024e040fa5c14661f9193a" + integrity sha1-0yHbxNowuovzAk4ED6XBRmH5GTo= + dependencies: + language-subtag-registry "~0.3.2" + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + 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" + integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= + dependencies: + 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" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + 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" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== + dependencies: + 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" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + 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.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +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== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@^2.0.0, 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" + integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +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" + integrity sha512-s885kWvnIlxsUFHq9UGyIyLiuD0G3BUC/xrH0CEnH5lHEWkwQcHOORgbDF0hbrW9vr/7am4ETfX4A7M6DjrE7Q== + dependencies: + "@next/env" "12.1.0" + caniuse-lite "^1.0.30001283" + postcss "8.4.5" + styled-jsx "5.0.0" + use-subscription "1.5.1" + optionalDependencies: + "@next/swc-android-arm64" "12.1.0" + "@next/swc-darwin-arm64" "12.1.0" + "@next/swc-darwin-x64" "12.1.0" + "@next/swc-linux-arm-gnueabihf" "12.1.0" + "@next/swc-linux-arm64-gnu" "12.1.0" + "@next/swc-linux-arm64-musl" "12.1.0" + "@next/swc-linux-x64-gnu" "12.1.0" + "@next/swc-linux-x64-musl" "12.1.0" + "@next/swc-win32-arm64-msvc" "12.1.0" + "@next/swc-win32-ia32-msvc" "12.1.0" + "@next/swc-win32-x64-msvc" "12.1.0" + +node-domexception@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + +node-fetch@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.2.tgz#16d33fbe32ca7c6ca1ca8ba5dfea1dd885c59f04" + integrity sha512-Cwhq1JFIoon15wcIkFzubVNFE5GvXGV82pKf4knXXjvGmn7RJKcypeuqcVNZMGDZsAFWyIRya/anwAJr7TWJ7w== + dependencies: + data-uri-to-buffer "^4.0.0" + 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== + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-inspect@^1.11.0, object-inspect@^1.9.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" + integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== + +object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + has-symbols "^1.0.1" + object-keys "^1.1.1" + +object.entries@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.5.tgz#e1acdd17c4de2cd96d5a08487cfb9db84d881861" + integrity sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + +object.fromentries@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.5.tgz#7b37b205109c21e741e605727fe8b0ad5fa08251" + integrity sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + +object.hasown@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.0.tgz#7232ed266f34d197d15cac5880232f7a4790afe5" + integrity sha512-MhjYRfj3GBlhSkDHo6QmvgjRLXQ2zndabdf3nX0yTyZK9rPfxb6uRpAac8HXNLy1GpqWtZ81Qh4v3uOls2sRAg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.19.1" + +object.values@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac" + integrity sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" + +p-limit@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== + 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" + integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= + 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" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + 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" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.6, path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-type@^4.0.0: + version "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: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +postcss@8.4.5: + version "8.4.5" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.5.tgz#bae665764dfd4c6fcc24dc0fdf7e7aa00cc77f95" + integrity sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg== + dependencies: + nanoid "^3.1.30" + picocolors "^1.0.0" + source-map-js "^1.0.1" + +prelude-ls@^1.2.1: + version "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" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + 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: + 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" + integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + scheduler "^0.20.2" + +react-is@^16.13.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-notifications-component@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/react-notifications-component/-/react-notifications-component-3.4.1.tgz#3670aa17210a4e63ba3b4f553853bd99ab6a0150" + integrity sha512-vS/RLdz+VlXZz0dbK+LCcdhgUdUPi1BvSo7mVp58AQDpixI9emGwI0uISXhiTSjqFn/cPibPlJOJQ8kcvgmUrQ== + +react@17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" + integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== + dependencies: + 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: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + 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" + +regenerator-runtime@^0.13.4: + version "0.13.9" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== + +regexp.prototype.flags@^1.3.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz#b3f4c0059af9e47eca9f3f660e51d81307e72307" + integrity sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +regexpp@^3.2.0: + version "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: + version "1.22.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" + integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== + dependencies: + is-core-module "^2.8.1" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +resolve@^2.0.0-next.3: + version "2.0.0-next.3" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.3.tgz#d41016293d4a8586a39ca5d9b5f15cbea1f55e46" + integrity sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q== + dependencies: + 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" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + 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== + dependencies: + glob "^7.0.0" + lodash "^4.17.11" + scss-tokenizer "^0.3.0" + yargs "^17.2.1" + +scheduler@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" + integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== + dependencies: + 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: + 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" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +signal-exit@^3.0.0, signal-exit@^3.0.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: + 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" + integrity sha512-6WgDX8HmQqvEd7J+G6VtAahhsQIssiZ8zl7zKh1VDMFyL3hRTJP4FTNA3RbIp2TOQ9AYNDcc7e3fH0Qbup+DBg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + get-intrinsic "^1.1.1" + has-symbols "^1.0.2" + internal-slot "^1.0.3" + regexp.prototype.flags "^1.3.1" + side-channel "^1.0.4" + +string.prototype.trimend@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" + integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +string.prototype.trimstart@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" + integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== + dependencies: + 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: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity 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" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +styled-jsx@5.0.0: + version "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" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +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" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + 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" + integrity sha512-nWuffZppoaYK0vQ1SQmkSsQzJoHA4s6uzdb2waRpD806x9yfq153AdVsWz4je2qZcW+pENrMQXbGQ3sMCkXuhw== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.1" + minimist "^1.2.0" + strip-bom "^3.0.0" + +tslib@^1.8.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + 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" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + 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" + integrity sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg== + +unbox-primitive@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" + integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== + dependencies: + function-bind "^1.1.1" + has-bigints "^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" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +use-subscription@1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.5.1.tgz#73501107f02fad84c6dd57965beb0b75c68c42d1" + integrity sha512-Xv2a1P/yReAjAbhylMfFplFKj9GssgTwN7RlcTxBujFQcloStWNDQdc4g4NRWH9xS4i/FDk04vQBptAXoF3VcA== + 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" + integrity sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA== + +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which@^2.0.1, which@^2.0.2: + 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/digitalocean/files/etc/update-motd.d/99-one-click b/hosting/digitalocean/files/etc/update-motd.d/99-one-click index 0f087a26ee..3fbdbe0146 100644 --- a/hosting/digitalocean/files/etc/update-motd.d/99-one-click +++ b/hosting/digitalocean/files/etc/update-motd.d/99-one-click @@ -12,7 +12,7 @@ All ports are BLOCKED except 22 (SSH), 80 (HTTP), 443 (HTTPS), and 10000 * Budibase website: http://budibase.com -For help and more information, visit https://docs.budibase.com/self-hosting/hosting-methods/digitalocean +For help and more information, visit https://docs.budibase.com/docs/digitalocean ******************************************************************************** To delete this message of the day: rm -rf $(readlink -f ${0}) diff --git a/hosting/docker-compose.dev.yaml b/hosting/docker-compose.dev.yaml index df403c0a22..43b8526e9e 100644 --- a/hosting/docker-compose.dev.yaml +++ b/hosting/docker-compose.dev.yaml @@ -5,7 +5,7 @@ version: "3" services: minio-service: container_name: budi-minio-dev - restart: always + restart: on-failure image: minio/minio volumes: - minio_data:/data @@ -23,7 +23,7 @@ services: proxy-service: container_name: budi-nginx-dev - restart: always + restart: on-failure image: nginx:latest volumes: - ./.generated-nginx.dev.conf:/etc/nginx/nginx.conf @@ -38,7 +38,7 @@ services: couchdb-service: # platform: linux/amd64 container_name: budi-couchdb-dev - restart: always + restart: on-failure image: ibmcom/couchdb3 environment: - COUCHDB_PASSWORD=${COUCH_DB_PASSWORD} @@ -59,7 +59,7 @@ services: redis-service: container_name: budi-redis-dev - restart: always + restart: on-failure image: redis command: redis-server --requirepass ${REDIS_PASSWORD} ports: diff --git a/hosting/docker-compose.yaml b/hosting/docker-compose.yaml index 8143be54b7..f3055b19fa 100644 --- a/hosting/docker-compose.yaml +++ b/hosting/docker-compose.yaml @@ -4,7 +4,7 @@ version: "3" services: app-service: - restart: always + restart: unless-stopped image: budibase.docker.scarf.sh/budibase/apps container_name: bbapps environment: @@ -28,7 +28,7 @@ services: - redis-service worker-service: - restart: always + restart: unless-stopped image: budibase.docker.scarf.sh/budibase/worker container_name: bbworker environment: @@ -53,7 +53,7 @@ services: - couch-init minio-service: - restart: always + restart: unless-stopped image: minio/minio volumes: - minio_data:/data @@ -69,7 +69,7 @@ services: retries: 3 proxy-service: - restart: always + restart: unless-stopped ports: - "${MAIN_PORT}:10000" container_name: bbproxy @@ -81,7 +81,7 @@ services: - couchdb-service couchdb-service: - restart: always + restart: unless-stopped image: ibmcom/couchdb3 environment: - COUCHDB_PASSWORD=${COUCH_DB_PASSWORD} @@ -98,13 +98,14 @@ services: command: ["sh","-c","sleep 10 && $${PUT_CALL}/_users && $${PUT_CALL}/_replicator; fg;"] redis-service: - restart: always + restart: unless-stopped image: redis command: redis-server --requirepass ${REDIS_PASSWORD} volumes: - redis_data:/data watchtower-service: + restart: always image: containrrr/watchtower volumes: - /var/run/docker.sock:/var/run/docker.sock diff --git a/hosting/nginx.dev.conf.hbs b/hosting/nginx.dev.conf.hbs index 624b4c2653..9fc2345fb2 100644 --- a/hosting/nginx.dev.conf.hbs +++ b/hosting/nginx.dev.conf.hbs @@ -52,9 +52,8 @@ http { proxy_pass http://{{ address }}:4001; } - location /app/ { + location /app { proxy_pass http://{{ address }}:4001; - rewrite ^/app/(.*)$ /$1 break; } location /builder { @@ -76,6 +75,7 @@ http { 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; @@ -91,4 +91,4 @@ http { gzip off; gzip_comp_level 4; } -} \ No newline at end of file +} diff --git a/hosting/nginx.prod.conf.hbs b/hosting/nginx.prod.conf.hbs index f446c928fb..0ab7ed2c7e 100644 --- a/hosting/nginx.prod.conf.hbs +++ b/hosting/nginx.prod.conf.hbs @@ -19,18 +19,11 @@ http { tcp_nodelay on; server_tokens off; types_hash_max_size 2048; - {{#if compose}} - resolver 127.0.0.11 ipv6=off; - {{/if}} - {{#if k8s}} - resolver kube-dns.kube-system.svc.cluster.local valid=10s; - {{/if}} - + resolver {{ resolver }} valid=10s ipv6=off; # buffering - client_body_buffer_size 1K; client_header_buffer_size 1k; - client_max_body_size 10M; + client_max_body_size 20M; ignore_invalid_headers off; proxy_buffering off; @@ -55,7 +48,7 @@ http { add_header X-Frame-Options SAMEORIGIN always; add_header X-Content-Type-Options nosniff always; add_header X-XSS-Protection "1; mode=block" always; - add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.budi.live https://js.intercomcdn.com https://widget.intercom.io; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://rsms.me https://maxcdn.bootstrapcdn.com; object-src 'none'; base-uri 'self'; connect-src 'self' https://api-iam.intercom.io https://app.posthog.com wss://nexus-websocket-a.intercom.io; font-src 'self' data https://cdn.jsdelivr.net https://fonts.gstatic.com https://rsms.me https://maxcdn.bootstrapcdn.com; frame-src 'self'; img-src http: https: data; manifest-src 'self'; media-src 'self'; worker-src 'none';" always; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.budi.live https://js.intercomcdn.com https://widget.intercom.io; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://rsms.me https://maxcdn.bootstrapcdn.com; object-src 'none'; base-uri 'self'; connect-src 'self' https://api-iam.intercom.io https://app.posthog.com wss://nexus-websocket-a.intercom.io ; font-src 'self' data https://cdn.jsdelivr.net https://fonts.gstatic.com https://rsms.me https://maxcdn.bootstrapcdn.com; frame-src 'self' https:; img-src http: https: data; manifest-src 'self'; media-src 'self'; worker-src 'none';" always; # upstreams set $apps {{ apps }}; @@ -68,7 +61,6 @@ http { location /app { proxy_pass http://$apps:4002; - rewrite ^/app/(.*)$ /$1 break; } location = / { diff --git a/hosting/proxy/nginx.conf b/hosting/proxy/nginx.conf deleted file mode 100644 index 95b06d4fff..0000000000 --- a/hosting/proxy/nginx.conf +++ /dev/null @@ -1,145 +0,0 @@ -user nginx; -error_log /var/log/nginx/error.log debug; -pid /var/run/nginx.pid; -worker_processes auto; -worker_rlimit_nofile 33282; - -events { - worker_connections 1024; -} - -http { - limit_req_zone $binary_remote_addr zone=ratelimit:10m rate=20r/s; - include /etc/nginx/mime.types; - default_type application/octet-stream; - charset utf-8; - sendfile on; - tcp_nopush on; - tcp_nodelay on; - server_tokens off; - types_hash_max_size 2048; - - # buffering - client_body_buffer_size 1K; - client_header_buffer_size 1k; - client_max_body_size 1k; - ignore_invalid_headers off; - - - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for"'; - - map $http_upgrade $connection_upgrade { - default "upgrade"; - } - - server { - listen 10000 default_server; - listen [::]:10000 default_server; - server_name _; - client_max_body_size 1000m; - ignore_invalid_headers off; - proxy_buffering off; - port_in_redirect off; - - # Security Headers - add_header X-Frame-Options SAMEORIGIN always; - add_header X-Content-Type-Options nosniff always; - add_header X-XSS-Protection "1; mode=block" always; - add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.budi.live https://js.intercomcdn.com https://widget.intercom.io; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://rsms.me; object-src 'none'; base-uri 'self'; connect-src 'self' https://api-iam.intercom.io https://app.posthog.com wss://nexus-websocket-a.intercom.io; font-src 'self' data: https://cdn.jsdelivr.net https://fonts.gstatic.com https://rsms.me; frame-src 'self'; img-src http: https: data:; manifest-src 'self'; media-src 'self'; worker-src 'none';" always; - - location /app { - proxy_pass http://app-service:4002; - rewrite ^/app/(.*)$ /$1 break; - } - - location = / { - port_in_redirect off; - proxy_pass http://app-service:4002; - } - - location = /v1/update { - proxy_pass http://watchtower-service:8080; - } - - location /builder/ { - port_in_redirect off; - proxy_http_version 1.1; - proxy_set_header Connection $connection_upgrade; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_pass http://app-service:4002; - } - - location ~ ^/(builder|app_) { - port_in_redirect off; - proxy_http_version 1.1; - proxy_set_header Connection $connection_upgrade; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_pass http://app-service:4002; - } - - location ~ ^/api/(system|admin|global)/ { - proxy_pass http://worker-service:4003; - } - - location /worker/ { - proxy_pass http://worker-service:4003; - rewrite ^/worker/(.*)$ /$1 break; - } - - location /api/ { - # calls to the API are rate limited with bursting - limit_req zone=ratelimit burst=20 nodelay; - - # 120s timeout on API requests - proxy_read_timeout 120s; - proxy_connect_timeout 120s; - proxy_send_timeout 120s; - - proxy_http_version 1.1; - proxy_set_header Connection $connection_upgrade; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - - proxy_pass http://app-service:4002; - } - - location /db/ { - proxy_pass http://couchdb-service:5984; - rewrite ^/db/(.*)$ /$1 break; - } - - location / { - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - - proxy_connect_timeout 300; - proxy_http_version 1.1; - proxy_set_header Connection ""; - chunked_transfer_encoding off; - proxy_pass http://minio-service:9000; - } - - client_header_timeout 60; - client_body_timeout 60; - keepalive_timeout 60; - - # gzip - gzip on; - gzip_vary on; - gzip_proxied any; - gzip_comp_level 6; - gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; - } -} \ No newline at end of file diff --git a/i18n/README.de.md b/i18n/README.de.md index 34a9164c3f..a2f4c3afb9 100644 --- a/i18n/README.de.md +++ b/i18n/README.de.md @@ -39,7 +39,7 @@

- Los Geht's + Los Geht's · Dokumentation · @@ -109,7 +109,7 @@ $ budi hosting --start 4. Lege einen Admin-Benutzer an. Gib die E-Mail und das Passwort für den neuen Admin-Benutzer ein. -Schon geschafft! Jetzt kann es losgehen mit der minutenschnellen Entwicklung deiner Tools. Für weitere Informationen und Tipps schau doch mal in unsere [Dokumentation](https://docs.budibase.com/getting-started). +Schon geschafft! Jetzt kann es losgehen mit der minutenschnellen Entwicklung deiner Tools. Für weitere Informationen und Tipps schau doch mal in unsere [Dokumentation](https://docs.budibase.com/docs/quickstart-tutorials).
diff --git a/i18n/README.es.md b/i18n/README.es.md index e0a5259cbc..7245dc8656 100644 --- a/i18n/README.es.md +++ b/i18n/README.es.md @@ -112,7 +112,7 @@ The Budibase builder runs in Electron, on Mac, PC and Linux. Follow the steps be Budibase wants to make sure anyone can use the tools we develop and we know a lot of people need to be able to host the apps they make on their own systems - that is why we've decided to try and make self hosting as easy as possible! -Currently, you can host your apps using Docker or Digital Ocean. The documentation for self-hosting can be found [here](https://docs.budibase.com/self-hosting/introduction-to-self-hosting). +Currently, you can host your apps using Docker or Digital Ocean. The documentation for self-hosting can be found [here](https://docs.budibase.com/docs/hosting-methods). [![Deploy to DO](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/droplets/new?onboarding_origin=marketplace&i=09038e&fleetUuid=bb04f9c8-1de8-4687-b2ae-1d5177a0535b&appId=77729671&type=applications&size=s-4vcpu-8gb®ion=nyc1&refcode=0caaa6085a82&image=budibase-20-04) diff --git a/lerna.json b/lerna.json index 1190c69981..0a7c854275 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "1.0.74-alpha.0", + "version": "1.0.98-alpha.1", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/package.json b/package.json index 5111ad204f..88aa681ad3 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,9 @@ "prettier-plugin-svelte": "^2.3.0", "rimraf": "^3.0.2", "rollup-plugin-replace": "^2.2.0", - "svelte": "^3.38.2" + "svelte": "^3.38.2", + "@typescript-eslint/parser": "4.28.0", + "typescript": "4.5.5" }, "scripts": { "setup": "node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev", @@ -31,24 +33,27 @@ "nuke:docker": "lerna run --parallel dev:stack:nuke", "clean": "lerna clean", "kill-port": "kill-port 4001", - "dev": "yarn run kill-port && lerna link && lerna run --parallel dev:builder --concurrency 1", - "dev:noserver": "lerna link && lerna run dev:stack:up && lerna run --parallel dev:builder --concurrency 1 --ignore @budibase/server --ignore @budibase/worker", - "dev:server": "lerna run --parallel dev:builder --concurrency 1 --scope @budibase/worker --scope @budibase/server", + "kill-builder": "kill-port 3000", + "kill-server": "kill-port 4001 4002", + "kill-all": "yarn run kill-builder && yarn run kill-server", + "dev": "yarn run kill-all && lerna link && lerna run --parallel dev:builder --concurrency 1", + "dev:noserver": "yarn run kill-builder && lerna link && lerna run dev:stack:up && lerna run --parallel dev:builder --concurrency 1 --ignore @budibase/server --ignore @budibase/worker", + "dev:server": "yarn run kill-server && lerna run --parallel dev:builder --concurrency 1 --scope @budibase/worker --scope @budibase/server", "test": "lerna run test", "lint:eslint": "eslint packages", "lint:prettier": "prettier --check \"packages/**/*.{js,ts,svelte}\"", "lint": "yarn run lint:eslint && yarn run lint:prettier", "lint:fix:eslint": "eslint --fix packages", - "lint:fix:prettier": "prettier --write \"packages/**/*.{js,ts,svelte}\"", - "lint:fix:ts": "lerna run lint:fix", - "lint:fix": "yarn run lint:fix:ts && yarn run lint:fix:prettier && yarn run lint:fix:eslint", - "test:e2e": "lerna run cy:test", - "test:e2e:ci": "lerna run cy:ci", + "lint:fix:prettier": "prettier --write \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\"", + "lint:fix": "yarn run lint:fix:prettier && yarn run lint:fix:eslint", + "test:e2e": "lerna run cy:test --stream", + "test:e2e:ci": "lerna run cy:ci --stream", + "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:proxy": "docker build hosting/proxy -t proxy-service", - "build:docker:proxy:compose": "lerna run generate:proxy:compose && npm run build:docker:proxy", - "build:docker:proxy:preprod": "lerna run generate:proxy:preprod && npm run build:docker:proxy", - "build:docker:proxy:prod": "lerna run generate:proxy:prod && npm run build:docker:proxy", + "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:prod": "node scripts/proxy/generateProxyConfig prod && npm run build:docker:proxy", "build:docker:selfhost": "lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh latest && cd -", "build:docker:develop": "node scripts/pinVersions && lerna run build:docker && npm run build:docker:proxy:compose && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh develop && cd -", "build:docker:airgap": "node hosting/scripts/airgapped/airgappedDockerBuild", diff --git a/packages/backend-core/encryption.js b/packages/backend-core/encryption.js new file mode 100644 index 0000000000..4ccb6e3a99 --- /dev/null +++ b/packages/backend-core/encryption.js @@ -0,0 +1 @@ +module.exports = require("./src/security/encryption") diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index 83845e822f..4b761b7d42 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/backend-core", - "version": "1.0.74-alpha.0", + "version": "1.0.98-alpha.1", "description": "Budibase backend core libraries used in server and worker", "main": "src/index.js", "author": "Budibase", diff --git a/packages/backend-core/src/cache/user.js b/packages/backend-core/src/cache/user.js index 60a2d341a8..b10f854002 100644 --- a/packages/backend-core/src/cache/user.js +++ b/packages/backend-core/src/cache/user.js @@ -32,11 +32,10 @@ const populateFromDB = async (userId, tenantId) => { * @param {*} populateUser function to provide the user for re-caching. default to couch db * @returns */ -exports.getUser = async ( - userId, - tenantId = null, - populateUser = populateFromDB -) => { +exports.getUser = async (userId, tenantId = null, populateUser = null) => { + if (!populateUser) { + populateUser = populateFromDB + } if (!tenantId) { try { tenantId = getTenantId() diff --git a/packages/backend-core/src/cloud/accounts.js b/packages/backend-core/src/cloud/accounts.js index b2e8817ad6..5730bc67a5 100644 --- a/packages/backend-core/src/cloud/accounts.js +++ b/packages/backend-core/src/cloud/accounts.js @@ -22,3 +22,18 @@ exports.getAccount = async email => { return json[0] } + +exports.getStatus = async () => { + const response = await api.get(`/api/status`, { + headers: { + [Headers.API_KEY]: env.ACCOUNT_PORTAL_API_KEY, + }, + }) + const json = await response.json() + + if (response.status !== 200) { + throw new Error(`Error getting status`) + } + + return json +} diff --git a/packages/backend-core/src/context/index.js b/packages/backend-core/src/context/index.js index 968ad4eefb..ba9a7831db 100644 --- a/packages/backend-core/src/context/index.js +++ b/packages/backend-core/src/context/index.js @@ -1,5 +1,6 @@ const env = require("../environment") const { Headers } = require("../../constants") +const { SEPARATOR, DocumentTypes } = require("../db/constants") const cls = require("./FunctionContext") const { getCouch } = require("../db") const { getProdAppID, getDevelopmentAppID } = require("../db/conversions") @@ -42,8 +43,39 @@ exports.doInTenant = (tenantId, task) => { }) } +/** + * Given an app ID this will attempt to retrieve the tenant ID from it. + * @return {null|string} The tenant ID found within the app ID. + */ +exports.getTenantIDFromAppID = appId => { + if (!appId) { + return null + } + const split = appId.split(SEPARATOR) + const hasDev = split[1] === DocumentTypes.DEV + if ((hasDev && split.length === 3) || (!hasDev && split.length === 2)) { + return null + } + if (hasDev) { + return split[2] + } else { + return split[1] + } +} + +const setAppTenantId = appId => { + const appTenantId = this.getTenantIDFromAppID(appId) || this.DEFAULT_TENANT_ID + this.updateTenantId(appTenantId) +} + exports.doInAppContext = (appId, task) => { + if (!appId) { + throw new Error("appId is required") + } return cls.run(() => { + // set the app tenant id + setAppTenantId(appId) + // set the app ID cls.setOnContext(ContextKeys.APP_ID, appId) diff --git a/packages/backend-core/src/db/constants.js b/packages/backend-core/src/db/constants.js index b41a9a9c08..5ee8033e05 100644 --- a/packages/backend-core/src/db/constants.js +++ b/packages/backend-core/src/db/constants.js @@ -14,6 +14,7 @@ exports.DocumentTypes = { APP_METADATA: `${PRE_APP}${exports.SEPARATOR}metadata`, ROLE: "role", MIGRATIONS: "migrations", + DEV_INFO: "devinfo", } exports.StaticDatabases = { diff --git a/packages/backend-core/src/db/utils.js b/packages/backend-core/src/db/utils.js index c4dcb8248b..feb17c4129 100644 --- a/packages/backend-core/src/db/utils.js +++ b/packages/backend-core/src/db/utils.js @@ -9,11 +9,7 @@ const { APP_PREFIX, APP_DEV, } = require("./constants") -const { - getTenantId, - getTenantIDFromAppID, - getGlobalDBName, -} = require("../tenancy") +const { getTenantId, getGlobalDBName } = require("../tenancy") const fetch = require("node-fetch") const { getCouch } = require("./index") const { getAppMetadata } = require("../cache/appMetadata") @@ -30,6 +26,7 @@ const UNICODE_MAX = "\ufff0" exports.ViewNames = { USER_BY_EMAIL: "by_email", + BY_API_KEY: "by_api_key", } exports.StaticDatabases = StaticDatabases @@ -38,7 +35,6 @@ exports.DocumentTypes = DocumentTypes exports.APP_PREFIX = APP_PREFIX exports.APP_DEV = exports.APP_DEV_PREFIX = APP_DEV exports.SEPARATOR = SEPARATOR -exports.getTenantIDFromAppID = getTenantIDFromAppID exports.isDevApp = isDevApp exports.isProdAppID = isProdAppID exports.isDevAppID = isDevAppID @@ -67,6 +63,7 @@ function getDocParams(docType, docId = null, otherProps = {}) { endkey: `${docType}${SEPARATOR}${docId}${UNICODE_MAX}`, } } +exports.getDocParams = getDocParams /** * Generates a new workspace ID. @@ -339,6 +336,14 @@ const getConfigParams = ({ type, workspace, user }, otherProps = {}) => { } } +/** + * Generates a new dev info document ID - this is scoped to a user. + * @returns {string} The new dev info ID which info for dev (like api key) can be stored under. + */ +const generateDevInfoID = userId => { + return `${DocumentTypes.DEV_INFO}${SEPARATOR}${userId}` +} + /** * Returns the most granular configuration document from the DB based on the type, workspace and userID passed. * @param {Object} db - db instance to query @@ -454,3 +459,4 @@ exports.generateConfigID = generateConfigID exports.getConfigParams = getConfigParams exports.getScopedFullConfig = getScopedFullConfig exports.generateNewUsageQuotaDoc = generateNewUsageQuotaDoc +exports.generateDevInfoID = generateDevInfoID diff --git a/packages/backend-core/src/db/views.js b/packages/backend-core/src/db/views.js index fd004ca0c2..e5be8e6b40 100644 --- a/packages/backend-core/src/db/views.js +++ b/packages/backend-core/src/db/views.js @@ -1,4 +1,5 @@ const { DocumentTypes, ViewNames } = require("./utils") +const { getGlobalDB } = require("../tenancy") function DesignDoc() { return { @@ -9,7 +10,8 @@ function DesignDoc() { } } -exports.createUserEmailView = async db => { +exports.createUserEmailView = async () => { + const db = getGlobalDB() let designDoc try { designDoc = await db.get("_design/database") @@ -31,3 +33,51 @@ exports.createUserEmailView = async db => { } await db.put(designDoc) } + +exports.createApiKeyView = async () => { + const db = getGlobalDB() + let designDoc + try { + designDoc = await db.get("_design/database") + } catch (err) { + designDoc = DesignDoc() + } + const view = { + map: `function(doc) { + if (doc._id.startsWith("${DocumentTypes.DEV_INFO}") && doc.apiKey) { + emit(doc.apiKey, doc.userId) + } + }`, + } + designDoc.views = { + ...designDoc.views, + [ViewNames.BY_API_KEY]: view, + } + await db.put(designDoc) +} + +exports.queryGlobalView = async (viewName, params, db = null) => { + const CreateFuncByName = { + [ViewNames.USER_BY_EMAIL]: exports.createUserEmailView, + [ViewNames.BY_API_KEY]: exports.createApiKeyView, + } + // can pass DB in if working with something specific + if (!db) { + db = getGlobalDB() + } + try { + let response = (await db.query(`database/${viewName}`, params)).rows + response = response.map(resp => + params.include_docs ? resp.doc : resp.value + ) + return response.length <= 1 ? response[0] : response + } catch (err) { + if (err != null && err.name === "not_found") { + const createFunc = CreateFuncByName[viewName] + await createFunc() + return exports.queryGlobalView(viewName, params) + } else { + throw err + } + } +} diff --git a/packages/backend-core/src/middleware/appTenancy.js b/packages/backend-core/src/middleware/appTenancy.js deleted file mode 100644 index b0430a0051..0000000000 --- a/packages/backend-core/src/middleware/appTenancy.js +++ /dev/null @@ -1,27 +0,0 @@ -const { - isMultiTenant, - updateTenantId, - isTenantIdSet, - DEFAULT_TENANT_ID, - updateAppId, -} = require("../tenancy") -const ContextFactory = require("../context/FunctionContext") -const { getTenantIDFromAppID } = require("../db/utils") - -module.exports = () => { - return ContextFactory.getMiddleware(ctx => { - // if not in multi-tenancy mode make sure its default and exit - if (!isMultiTenant()) { - updateTenantId(DEFAULT_TENANT_ID) - return - } - // if tenant ID already set no need to continue - if (isTenantIdSet()) { - return - } - const appId = ctx.appId ? ctx.appId : ctx.user ? ctx.user.appId : null - const tenantId = getTenantIDFromAppID(appId) || DEFAULT_TENANT_ID - updateTenantId(tenantId) - updateAppId(appId) - }) -} diff --git a/packages/backend-core/src/middleware/authenticated.js b/packages/backend-core/src/middleware/authenticated.js index 4978f7b9dc..ef982c799b 100644 --- a/packages/backend-core/src/middleware/authenticated.js +++ b/packages/backend-core/src/middleware/authenticated.js @@ -4,6 +4,9 @@ const { getUser } = require("../cache/user") const { getSession, updateSessionTTL } = require("../security/sessions") const { buildMatcherRegex, matches } = require("./matchers") const env = require("../environment") +const { SEPARATOR, ViewNames, queryGlobalView } = require("../../db") +const { getGlobalDB, doInTenant } = require("../tenancy") +const { decrypt } = require("../security/encryption") function finalise( ctx, @@ -16,6 +19,33 @@ function finalise( ctx.version = version } +async function checkApiKey(apiKey, populateUser) { + if (apiKey === env.INTERNAL_API_KEY) { + return { valid: true } + } + const decrypted = decrypt(apiKey) + const tenantId = decrypted.split(SEPARATOR)[0] + return doInTenant(tenantId, async () => { + const db = getGlobalDB() + // api key is encrypted in the database + const userId = await queryGlobalView( + ViewNames.BY_API_KEY, + { + key: apiKey, + }, + db + ) + if (userId) { + return { + valid: true, + user: await getUser(userId, tenantId, populateUser), + } + } else { + throw "Invalid API key" + } + }) +} + /** * This middleware is tenancy aware, so that it does not depend on other middlewares being used. * The tenancy modules should not be used here and it should be assumed that the tenancy context @@ -79,9 +109,19 @@ module.exports = ( const apiKey = ctx.request.headers[Headers.API_KEY] const tenantId = ctx.request.headers[Headers.TENANT_ID] // this is an internal request, no user made it - if (!authenticated && apiKey && apiKey === env.INTERNAL_API_KEY) { - authenticated = true - internal = true + if (!authenticated && apiKey) { + const populateUser = opts.populateUser ? opts.populateUser(ctx) : null + const { valid, user: foundUser } = await checkApiKey( + apiKey, + populateUser + ) + if (valid && foundUser) { + authenticated = true + user = foundUser + } else if (valid) { + authenticated = true + internal = true + } } if (!user && tenantId) { user = { tenantId } @@ -101,6 +141,7 @@ module.exports = ( // allow configuring for public access if ((opts && opts.publicAllowed) || publicEndpoint) { finalise(ctx, { authenticated: false, version, publicEndpoint }) + return next() } else { ctx.throw(err.status || 403, err) } diff --git a/packages/backend-core/src/middleware/index.js b/packages/backend-core/src/middleware/index.js index 5878479152..6c4c0d8883 100644 --- a/packages/backend-core/src/middleware/index.js +++ b/packages/backend-core/src/middleware/index.js @@ -6,7 +6,6 @@ const { authError } = require("./passport/utils") const authenticated = require("./authenticated") const auditLog = require("./auditLog") const tenancy = require("./tenancy") -const appTenancy = require("./appTenancy") const internalApi = require("./internalApi") const datasourceGoogle = require("./passport/datasource/google") const csrf = require("./csrf") @@ -19,7 +18,6 @@ module.exports = { authenticated, auditLog, tenancy, - appTenancy, authError, internalApi, datasource: { diff --git a/packages/backend-core/src/migrations/index.js b/packages/backend-core/src/migrations/index.js index 4d59f1925d..db0fe6b8ce 100644 --- a/packages/backend-core/src/migrations/index.js +++ b/packages/backend-core/src/migrations/index.js @@ -36,7 +36,7 @@ const runMigration = async (CouchDB, migration, options = {}) => { if (migrationType === exports.MIGRATION_TYPES.GLOBAL) { dbNames = [getGlobalDBName()] } else if (migrationType === exports.MIGRATION_TYPES.APP) { - const apps = await getAllApps(CouchDB, migration.opts) + const apps = await getAllApps(migration.opts) dbNames = apps.map(app => app.appId) } else { throw new Error( diff --git a/packages/backend-core/src/objectStore/index.js b/packages/backend-core/src/objectStore/index.js index b5d8475cee..2385149f4d 100644 --- a/packages/backend-core/src/objectStore/index.js +++ b/packages/backend-core/src/objectStore/index.js @@ -78,6 +78,7 @@ exports.ObjectStore = bucket => { const config = { s3ForcePathStyle: true, signatureVersion: "v4", + apiVersion: "2006-03-01", params: { Bucket: sanitizeBucket(bucket), }, @@ -102,17 +103,21 @@ exports.makeSureBucketExists = async (client, bucketName) => { .promise() } catch (err) { const promises = STATE.bucketCreationPromises + const doesntExist = err.statusCode === 404, + noAccess = err.statusCode === 403 if (promises[bucketName]) { await promises[bucketName] - } else if (err.statusCode === 404) { - // bucket doesn't exist create it - promises[bucketName] = client - .createBucket({ - Bucket: bucketName, - }) - .promise() - await promises[bucketName] - delete promises[bucketName] + } else if (doesntExist || noAccess) { + if (doesntExist) { + // bucket doesn't exist create it + promises[bucketName] = client + .createBucket({ + Bucket: bucketName, + }) + .promise() + await promises[bucketName] + delete promises[bucketName] + } // public buckets are quite hidden in the system, make sure // no bucket is set accidentally if (PUBLIC_BUCKETS.includes(bucketName)) { @@ -124,7 +129,7 @@ exports.makeSureBucketExists = async (client, bucketName) => { .promise() } } else { - throw err + throw new Error("Unable to write to object store bucket.") } } } diff --git a/packages/backend-core/src/redis/utils.js b/packages/backend-core/src/redis/utils.js index 3461d2a511..4c2b2f5cae 100644 --- a/packages/backend-core/src/redis/utils.js +++ b/packages/backend-core/src/redis/utils.js @@ -22,12 +22,25 @@ exports.Databases = { exports.SEPARATOR = SEPARATOR exports.getRedisOptions = (clustered = false) => { - const [host, port, ...rest] = REDIS_URL.split(":") + let password = REDIS_PASSWORD + let url = REDIS_URL.split("//") + // get rid of the protocol + url = url.length > 1 ? url[1] : url[0] + // check for a password etc + url = url.split("@") + if (url.length > 1) { + // get the password + password = url[0].split(":")[1] + url = url[1] + } else { + url = url[0] + } + const [host, port] = url.split(":") let redisProtocolUrl // fully qualified redis URL - if (rest.length && /rediss?/.test(host)) { + if (/rediss?:\/\//.test(REDIS_URL)) { redisProtocolUrl = REDIS_URL } @@ -37,13 +50,13 @@ exports.getRedisOptions = (clustered = false) => { if (clustered) { opts.redisOptions = {} opts.redisOptions.tls = {} - opts.redisOptions.password = REDIS_PASSWORD + opts.redisOptions.password = password opts.slotsRefreshTimeout = SLOT_REFRESH_MS opts.dnsLookup = (address, callback) => callback(null, address) } else { opts.host = host opts.port = port - opts.password = REDIS_PASSWORD + opts.password = password } return { opts, host, port, redisProtocolUrl } } diff --git a/packages/backend-core/src/security/apiKeys.js b/packages/backend-core/src/security/apiKeys.js new file mode 100644 index 0000000000..e90418abb8 --- /dev/null +++ b/packages/backend-core/src/security/apiKeys.js @@ -0,0 +1 @@ +exports.lookupApiKey = async () => {} diff --git a/packages/backend-core/src/security/encryption.js b/packages/backend-core/src/security/encryption.js new file mode 100644 index 0000000000..c31f597652 --- /dev/null +++ b/packages/backend-core/src/security/encryption.js @@ -0,0 +1,33 @@ +const crypto = require("crypto") +const env = require("../environment") + +const ALGO = "aes-256-ctr" +const SECRET = env.JWT_SECRET +const SEPARATOR = "-" +const ITERATIONS = 10000 +const RANDOM_BYTES = 16 +const STRETCH_LENGTH = 32 + +function stretchString(string, salt) { + return crypto.pbkdf2Sync(string, salt, ITERATIONS, STRETCH_LENGTH, "sha512") +} + +exports.encrypt = input => { + const salt = crypto.randomBytes(RANDOM_BYTES) + const stretched = stretchString(SECRET, salt) + const cipher = crypto.createCipheriv(ALGO, stretched, salt) + const base = cipher.update(input) + const final = cipher.final() + const encrypted = Buffer.concat([base, final]).toString("hex") + return `${salt.toString("hex")}${SEPARATOR}${encrypted}` +} + +exports.decrypt = input => { + const [salt, encrypted] = input.split(SEPARATOR) + const saltBuffer = Buffer.from(salt, "hex") + const stretched = stretchString(SECRET, saltBuffer) + const decipher = crypto.createDecipheriv(ALGO, stretched, saltBuffer) + const base = decipher.update(Buffer.from(encrypted, "hex")) + const final = decipher.final() + return Buffer.concat([base, final]).toString() +} diff --git a/packages/backend-core/src/security/permissions.js b/packages/backend-core/src/security/permissions.js index 3b05c10e20..28044a5129 100644 --- a/packages/backend-core/src/security/permissions.js +++ b/packages/backend-core/src/security/permissions.js @@ -10,6 +10,7 @@ const PermissionLevels = { // these are the global types, that govern the underlying default behaviour const PermissionTypes = { + APP: "app", TABLE: "table", USER: "user", AUTOMATION: "automation", diff --git a/packages/backend-core/src/tenancy/tenancy.js b/packages/backend-core/src/tenancy/tenancy.js index 8360198b60..24acc16862 100644 --- a/packages/backend-core/src/tenancy/tenancy.js +++ b/packages/backend-core/src/tenancy/tenancy.js @@ -1,6 +1,11 @@ const { getDB } = require("../db") -const { SEPARATOR, StaticDatabases, DocumentTypes } = require("../db/constants") -const { getTenantId, DEFAULT_TENANT_ID, isMultiTenant } = require("../context") +const { SEPARATOR, StaticDatabases } = require("../db/constants") +const { + getTenantId, + DEFAULT_TENANT_ID, + isMultiTenant, + getTenantIDFromAppID, +} = require("../context") const env = require("../environment") const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants @@ -118,26 +123,6 @@ exports.getTenantUser = async identifier => { } } -/** - * Given an app ID this will attempt to retrieve the tenant ID from it. - * @return {null|string} The tenant ID found within the app ID. - */ -exports.getTenantIDFromAppID = appId => { - if (!appId) { - return null - } - const split = appId.split(SEPARATOR) - const hasDev = split[1] === DocumentTypes.DEV - if ((hasDev && split.length === 3) || (!hasDev && split.length === 2)) { - return null - } - if (hasDev) { - return split[2] - } else { - return split[1] - } -} - exports.isUserInAppTenant = (appId, user = null) => { let userTenantId if (user) { @@ -145,7 +130,7 @@ exports.isUserInAppTenant = (appId, user = null) => { } else { userTenantId = getTenantId() } - const tenantId = exports.getTenantIDFromAppID(appId) || DEFAULT_TENANT_ID + const tenantId = getTenantIDFromAppID(appId) || DEFAULT_TENANT_ID return tenantId === userTenantId } diff --git a/packages/backend-core/src/utils.js b/packages/backend-core/src/utils.js index 45fb4acd55..8909f62995 100644 --- a/packages/backend-core/src/utils.js +++ b/packages/backend-core/src/utils.js @@ -3,10 +3,11 @@ const { SEPARATOR, ViewNames, generateGlobalUserID, + getAllApps, } = require("./db/utils") const jwt = require("jsonwebtoken") const { options } = require("./middleware/passport/jwt") -const { createUserEmailView } = require("./db/views") +const { queryGlobalView } = require("./db/views") const { Headers, UserStatus, Cookies, MAX_VALID_DATE } = require("./constants") const { getGlobalDB, @@ -20,8 +21,10 @@ const { hash } = require("./hashing") const userCache = require("./cache/user") const env = require("./environment") const { getUserSessions, invalidateSessions } = require("./security/sessions") +const tenancy = require("./tenancy") const APP_PREFIX = DocumentTypes.APP + SEPARATOR +const PROD_APP_PREFIX = "/app/" function confirmAppId(possibleAppId) { return possibleAppId && possibleAppId.startsWith(APP_PREFIX) @@ -29,16 +32,35 @@ function confirmAppId(possibleAppId) { : undefined } +async function resolveAppUrl(ctx) { + const appUrl = ctx.path.split("/")[2] + let possibleAppUrl = `/${appUrl.toLowerCase()}` + + let tenantId = tenancy.getTenantId() + if (!env.SELF_HOSTED && ctx.subdomains.length) { + // always use the tenant id from the url in cloud + tenantId = ctx.subdomains[0] + } + + // search prod apps for a url that matches + const apps = await tenancy.doInTenant(tenantId, () => + getAllApps({ dev: false }) + ) + const app = apps.filter( + a => a.url && a.url.toLowerCase() === possibleAppUrl + )[0] + + return app && app.appId ? app.appId : undefined +} + /** * Given a request tries to find the appId, which can be located in various places * @param {object} ctx The main request body to look through. * @returns {string|undefined} If an appId was found it will be returned. */ -exports.getAppId = ctx => { - const options = [ctx.headers[Headers.APP_ID], ctx.params.appId] - if (ctx.subdomains) { - options.push(ctx.subdomains[1]) - } +exports.getAppIdFromCtx = async ctx => { + // look in headers + const options = [ctx.headers[Headers.APP_ID]] let appId for (let option of options) { appId = confirmAppId(option) @@ -47,16 +69,24 @@ exports.getAppId = ctx => { } } - // look in body if can't find it in subdomain + // look in body if (!appId && ctx.request.body && ctx.request.body.appId) { 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 !== 0) { + if (!appId && appPath.length) { appId = confirmAppId(appPath[0]) } + + // look in the url - prod app + if (!appId && ctx.path.startsWith(PROD_APP_PREFIX)) { + appId = confirmAppId(await resolveAppUrl(ctx)) + } + return appId } @@ -139,25 +169,11 @@ exports.getGlobalUserByEmail = async email => { if (email == null) { throw "Must supply an email address to view" } - const db = getGlobalDB() - try { - let users = ( - await db.query(`database/${ViewNames.USER_BY_EMAIL}`, { - key: email.toLowerCase(), - include_docs: true, - }) - ).rows - users = users.map(user => user.doc) - return users.length <= 1 ? users[0] : users - } catch (err) { - if (err != null && err.name === "not_found") { - await createUserEmailView(db) - return exports.getGlobalUserByEmail(email) - } else { - throw err - } - } + return queryGlobalView(ViewNames.USER_BY_EMAIL, { + key: email.toLowerCase(), + include_docs: true, + }) } exports.saveUser = async ( diff --git a/packages/bbui/package.json b/packages/bbui/package.json index 5fbc32b60c..da87791608 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "1.0.74-alpha.0", + "version": "1.0.98-alpha.1", "license": "MPL-2.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", @@ -38,6 +38,7 @@ ], "dependencies": { "@adobe/spectrum-css-workflow-icons": "^1.2.1", + "@budibase/string-templates": "^1.0.98-alpha.1", "@spectrum-css/actionbutton": "^1.0.1", "@spectrum-css/actiongroup": "^1.0.1", "@spectrum-css/avatar": "^3.0.2", diff --git a/packages/bbui/src/Banner/Banner.svelte b/packages/bbui/src/Banner/Banner.svelte index f28ee09d9c..f41fb5f803 100644 --- a/packages/bbui/src/Banner/Banner.svelte +++ b/packages/bbui/src/Banner/Banner.svelte @@ -57,3 +57,10 @@ {/if} + + diff --git a/packages/bbui/src/Banner/BannerDisplay.svelte b/packages/bbui/src/Banner/BannerDisplay.svelte new file mode 100644 index 0000000000..aad742b1bd --- /dev/null +++ b/packages/bbui/src/Banner/BannerDisplay.svelte @@ -0,0 +1,31 @@ + + + + + + + diff --git a/packages/bbui/src/Button/Button.svelte b/packages/bbui/src/Button/Button.svelte index 8e5cc5468c..a3d2df069a 100644 --- a/packages/bbui/src/Button/Button.svelte +++ b/packages/bbui/src/Button/Button.svelte @@ -29,6 +29,7 @@ {disabled} on:click|preventDefault on:mouseover={() => (showTooltip = true)} + on:focus={() => (showTooltip = true)} on:mouseleave={() => (showTooltip = false)} > {#if icon} diff --git a/packages/bbui/src/ColorPicker/ColorPicker.svelte b/packages/bbui/src/ColorPicker/ColorPicker.svelte index 1fa950fadc..52a877415b 100644 --- a/packages/bbui/src/ColorPicker/ColorPicker.svelte +++ b/packages/bbui/src/ColorPicker/ColorPicker.svelte @@ -10,6 +10,7 @@ export let value export let size = "M" export let spectrumTheme + export let alignRight = false let open = false @@ -133,6 +134,7 @@ use:clickOutside={() => (open = false)} transition:fly={{ y: -20, duration: 200 }} class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open" + class:spectrum-Popover--align-right={alignRight} > {#each categories as category}
@@ -250,6 +252,9 @@ align-items: stretch; gap: var(--spacing-xl); } + .spectrum-Popover--align-right { + right: 0; + } .colors { display: grid; grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr; diff --git a/packages/bbui/src/Drawer/Drawer.svelte b/packages/bbui/src/Drawer/Drawer.svelte index e6b63b7df8..e5eacb1a44 100644 --- a/packages/bbui/src/Drawer/Drawer.svelte +++ b/packages/bbui/src/Drawer/Drawer.svelte @@ -1,5 +1,4 @@ {#if visible} -
+
{title} diff --git a/packages/bbui/src/Form/Core/Checkbox.svelte b/packages/bbui/src/Form/Core/Checkbox.svelte index a5b366c262..8384c8ca09 100644 --- a/packages/bbui/src/Form/Core/Checkbox.svelte +++ b/packages/bbui/src/Form/Core/Checkbox.svelte @@ -47,7 +47,9 @@ - {text || ""} + {#if text} + {text} + {/if} diff --git a/packages/bbui/src/Stores/banner.js b/packages/bbui/src/Stores/banner.js new file mode 100644 index 0000000000..81a9ee2204 --- /dev/null +++ b/packages/bbui/src/Stores/banner.js @@ -0,0 +1,37 @@ +import { writable } from "svelte/store" + +export function createBannerStore() { + const DEFAULT_CONFIG = {} + + const banner = writable(DEFAULT_CONFIG) + + const show = async ( + // eslint-disable-next-line + config = { message, type, extraButtonText, extraButtonAction, onChange } + ) => { + banner.update(store => { + return { + ...store, + ...config, + } + }) + } + + const showStatus = async () => { + const config = { + message: "Some systems are experiencing issues", + type: "negative", + extraButtonText: "View Status", + extraButtonAction: () => window.open("https://status.budibase.com/"), + } + + await show(config) + } + + return { + subscribe: banner.subscribe, + showStatus, + } +} + +export const banner = createBannerStore() diff --git a/packages/bbui/src/Stores/notifications.js b/packages/bbui/src/Stores/notifications.js index 640f8218c4..74eed8628a 100644 --- a/packages/bbui/src/Stores/notifications.js +++ b/packages/bbui/src/Stores/notifications.js @@ -60,7 +60,7 @@ export const createNotificationStore = () => { } function id() { - return "_" + Math.random().toString(36).substr(2, 9) + return "_" + Math.random().toString(36).slice(2, 9) } export const notifications = createNotificationStore() diff --git a/packages/bbui/src/Table/AttachmentRenderer.svelte b/packages/bbui/src/Table/AttachmentRenderer.svelte index 797ca93695..97ce1394cc 100644 --- a/packages/bbui/src/Table/AttachmentRenderer.svelte +++ b/packages/bbui/src/Table/AttachmentRenderer.svelte @@ -17,14 +17,16 @@ {#each attachments as attachment} {#if isImage(attachment.extension)} - {attachment.extension} +
+ {attachment.extension} +
{:else}
- {attachment.extension} + + {attachment.extension} +
{/if} @@ -38,12 +40,15 @@ height: 32px; max-width: 64px; } + .center, .file { - height: 32px; display: flex; flex-direction: row; justify-content: flex-start; align-items: center; + } + .file { + height: 32px; padding: 0 8px; color: var(--spectrum-global-color-gray-800); border: 1px solid var(--spectrum-global-color-gray-300); diff --git a/packages/bbui/src/Table/BoldRenderer.svelte b/packages/bbui/src/Table/BoldRenderer.svelte index ea882d538e..ae9ca24c42 100644 --- a/packages/bbui/src/Table/BoldRenderer.svelte +++ b/packages/bbui/src/Table/BoldRenderer.svelte @@ -7,5 +7,9 @@ diff --git a/packages/bbui/src/Table/CellRenderer.svelte b/packages/bbui/src/Table/CellRenderer.svelte index 9a53fd0169..4dda31240a 100644 --- a/packages/bbui/src/Table/CellRenderer.svelte +++ b/packages/bbui/src/Table/CellRenderer.svelte @@ -6,6 +6,7 @@ import AttachmentRenderer from "./AttachmentRenderer.svelte" import ArrayRenderer from "./ArrayRenderer.svelte" import InternalRenderer from "./InternalRenderer.svelte" + import { processStringSync } from "@budibase/string-templates" export let row export let schema @@ -28,10 +29,33 @@ $: type = schema?.type ?? "string" $: customRenderer = customRenderers?.find(x => x.column === schema?.name) $: renderer = customRenderer?.component ?? typeMap[type] ?? StringRenderer + $: width = schema?.width || "150px" + $: cellValue = getCellValue(value, schema.template) + + const getCellValue = (value, template) => { + if (!template) { + return value + } + return processStringSync(template, { value }) + } -{#if renderer && (customRenderer || (value != null && value !== ""))} - - - +{#if renderer && (customRenderer || (cellValue != null && cellValue !== ""))} +
+ + + +
{/if} + + diff --git a/packages/bbui/src/Table/CodeRenderer.svelte b/packages/bbui/src/Table/CodeRenderer.svelte index a75bec663c..5658851197 100644 --- a/packages/bbui/src/Table/CodeRenderer.svelte +++ b/packages/bbui/src/Table/CodeRenderer.svelte @@ -3,3 +3,12 @@ {value} + + diff --git a/packages/bbui/src/Table/DateTimeRenderer.svelte b/packages/bbui/src/Table/DateTimeRenderer.svelte index ff750cecd8..5d856968e7 100644 --- a/packages/bbui/src/Table/DateTimeRenderer.svelte +++ b/packages/bbui/src/Table/DateTimeRenderer.svelte @@ -17,6 +17,8 @@ diff --git a/packages/bbui/src/Table/InternalRenderer.svelte b/packages/bbui/src/Table/InternalRenderer.svelte index 858d51f128..7e2dd0b2aa 100644 --- a/packages/bbui/src/Table/InternalRenderer.svelte +++ b/packages/bbui/src/Table/InternalRenderer.svelte @@ -43,11 +43,3 @@
- - diff --git a/packages/bbui/src/Table/SelectEditRenderer.svelte b/packages/bbui/src/Table/SelectEditRenderer.svelte index e7a70979a0..c6eafa3ed0 100644 --- a/packages/bbui/src/Table/SelectEditRenderer.svelte +++ b/packages/bbui/src/Table/SelectEditRenderer.svelte @@ -8,9 +8,21 @@ export let allowEditRows = false -{#if allowSelectRows} - -{/if} -{#if allowEditRows} - Edit -{/if} +
+ {#if allowSelectRows} + + {/if} + {#if allowEditRows} + Edit + {/if} +
+ + diff --git a/packages/bbui/src/Table/StringRenderer.svelte b/packages/bbui/src/Table/StringRenderer.svelte index 6fd731f5ec..e881551bc8 100644 --- a/packages/bbui/src/Table/StringRenderer.svelte +++ b/packages/bbui/src/Table/StringRenderer.svelte @@ -8,6 +8,7 @@ div { overflow: hidden; text-overflow: ellipsis; - width: 150px; + white-space: nowrap; + max-width: var(--max-cell-width); } diff --git a/packages/bbui/src/Table/Table.svelte b/packages/bbui/src/Table/Table.svelte index a40ec2a45b..1d957b3f30 100644 --- a/packages/bbui/src/Table/Table.svelte +++ b/packages/bbui/src/Table/Table.svelte @@ -4,6 +4,8 @@ import CellRenderer from "./CellRenderer.svelte" import SelectEditRenderer from "./SelectEditRenderer.svelte" import { cloneDeep, deepGet } from "../helpers" + import ProgressCircle from "../ProgressCircle/ProgressCircle.svelte" + import Checkbox from "../Form/Checkbox.svelte" /** * The expected schema is our normal couch schemas for our tables. @@ -14,6 +16,11 @@ * sortable: Set to false to disable sorting data by a certain column * editable: Set to false to disable editing a certain column if the * allowEditColumns prop is true + * width: the width of the column + * align: the alignment of the column + * template: a HBS or JS binding to use as the value + * background: the background color + * color: the text color */ export let data = [] export let schema = {} @@ -25,16 +32,16 @@ export let allowEditRows = true export let allowEditColumns = true export let selectedRows = [] - export let editColumnTitle = "Edit" export let customRenderers = [] export let disableSorting = false + export let autoSortColumns = true + export let compact = false const dispatch = createEventDispatcher() // Config - const rowHeight = 55 const headerHeight = 36 - const rowPreload = 5 + $: rowHeight = compact ? 46 : 55 // Sorting state let sortColumn @@ -43,33 +50,39 @@ // Table state let height = 0 let loaded = false + let checkboxStatus = false + $: schema = fixSchema(schema) $: if (!loading) loaded = true - $: rows = data ?? [] - $: visibleRowCount = getVisibleRowCount(loaded, height, rows.length, rowCount) - $: contentStyle = getContentStyle(visibleRowCount, rowCount) - $: sortedRows = sortRows(rows, sortColumn, sortOrder) - $: fields = getFields(schema, showAutoColumns) - $: showEditColumn = allowEditRows || allowSelectRows - - // Scrolling state - let timeout - let nextScrollTop = 0 - let scrollTop = 0 - $: firstVisibleRow = calculateFirstVisibleRow(scrollTop) - $: lastVisibleRow = calculateLastVisibleRow( - firstVisibleRow, - visibleRowCount, - rows.length + $: fields = getFields(schema, showAutoColumns, autoSortColumns) + $: rows = fields?.length ? data || [] : [] + $: totalRowCount = rows?.length || 0 + $: visibleRowCount = getVisibleRowCount( + loaded, + height, + rows.length, + rowCount, + rowHeight ) + $: heightStyle = getHeightStyle( + visibleRowCount, + rowCount, + totalRowCount, + rowHeight + ) + $: sortedRows = sortRows(rows, sortColumn, sortOrder) + $: gridStyle = getGridStyle(fields, schema, showEditColumn) + $: showEditColumn = allowEditRows || allowSelectRows + $: cellStyles = computeCellStyles(schema) - // Reset state when data changes - $: rows.length, reset() - const reset = () => { - nextScrollTop = 0 - scrollTop = 0 - clearTimeout(timeout) - timeout = null + // Deselect the "select all" checkbox when the user navigates to a new page + $: { + let checkRowCount = rows.filter(o1 => + selectedRows.some(o2 => o1._id === o2._id) + ) + if (checkRowCount.length === 0) { + checkboxStatus = false + } } const fixSchema = schema => { @@ -90,7 +103,7 @@ return fixedSchema } - const getVisibleRowCount = (loaded, height, allRows, rowCount) => { + const getVisibleRowCount = (loaded, height, allRows, rowCount, rowHeight) => { if (!loaded) { return rowCount || 0 } @@ -100,11 +113,33 @@ return Math.min(allRows, Math.ceil(height / rowHeight)) } - const getContentStyle = (visibleRows, rowCount) => { - if (!rowCount || !visibleRows) { + const getHeightStyle = ( + visibleRowCount, + rowCount, + totalRowCount, + rowHeight + ) => { + if (!rowCount || !visibleRowCount || totalRowCount <= rowCount) { return "" } - return `height: ${headerHeight + visibleRows * (rowHeight + 1)}px;` + return `height: ${headerHeight + visibleRowCount * rowHeight}px;` + } + + const getGridStyle = (fields, schema, showEditColumn) => { + let style = "grid-template-columns:" + if (showEditColumn) { + style += " auto" + } + fields?.forEach(field => { + const fieldSchema = schema[field] + if (fieldSchema.width) { + style += ` ${fieldSchema.width}` + } else { + style += " minmax(auto, 1fr)" + } + }) + style += ";" + return style } const sortRows = (rows, sortColumn, sortOrder) => { @@ -143,14 +178,14 @@ return name || "" } - const getFields = (schema, showAutoColumns) => { + const getFields = (schema, showAutoColumns, autoSortColumns) => { let columns = [] let autoColumns = [] Object.entries(schema || {}).forEach(([field, fieldSchema]) => { if (!field || !fieldSchema) { return } - if (!fieldSchema?.autocolumn) { + if (!autoSortColumns || !fieldSchema?.autocolumn) { columns.push(fieldSchema) } else if (showAutoColumns) { autoColumns.push(fieldSchema) @@ -171,28 +206,6 @@ .map(column => column.name) } - const onScroll = event => { - nextScrollTop = event.target.scrollTop - if (timeout) { - return - } - timeout = setTimeout(() => { - scrollTop = nextScrollTop - timeout = null - }, 50) - } - - const calculateFirstVisibleRow = scrollTop => { - return Math.max(Math.floor(scrollTop / (rowHeight + 1)) - rowPreload, 0) - } - - const calculateLastVisibleRow = (firstRow, visibleRowCount, allRowCount) => { - if (visibleRowCount === 0) { - return -1 - } - return Math.min(firstRow + visibleRowCount + 2 * rowPreload, allRowCount) - } - const editColumn = (e, field) => { e.stopPropagation() dispatch("editcolumn", field) @@ -207,176 +220,270 @@ if (!allowSelectRows) { return } - if (selectedRows.includes(row)) { - selectedRows = selectedRows.filter(selectedRow => selectedRow !== row) + if (selectedRows.some(selectedRow => selectedRow._id === row._id)) { + selectedRows = selectedRows.filter( + selectedRow => selectedRow._id !== row._id + ) } else { selectedRows = [...selectedRows, row] } } + + const toggleSelectAll = e => { + const select = !!e.detail + if (select) { + // Add any rows which are not already in selected rows + rows.forEach(row => { + if (selectedRows.findIndex(x => x._id === row._id) === -1) { + selectedRows.push(row) + } + }) + } else { + // Remove any rows from selected rows that are in the current data set + selectedRows = selectedRows.filter(el => + rows.every(f => f._id !== el._id) + ) + } + } + + const computeCellStyles = schema => { + let styles = {} + Object.keys(schema || {}).forEach(field => { + styles[field] = "" + if (schema[field].color) { + styles[field] += `color: ${schema[field].color};` + } + if (schema[field].background) { + styles[field] += `background-color: ${schema[field].background};` + } + if (schema[field].align === "Center") { + styles[field] += "justify-content: center; text-align: center;" + } + if (schema[field].align === "Right") { + styles[field] += "justify-content: flex-end; text-align: right;" + } + }) + return styles + } -
+
{#if !loaded} -
+
+ +
{:else} -
-
-

- {#if fields.length} - - - {#if showEditColumn} - - {/if} - {#each fields as field} - - {/each} - - +
+ {#if fields.length} +
+ {#if showEditColumn} +
+ {#if allowSelectRows} + + {:else} + Edit + {/if} +
{/if} -
- {#if sortedRows?.length && fields.length} - {#each sortedRows as row, idx} - dispatch("click", row)} - on:click={() => toggleSelectRow(row)} - class="spectrum-Table-row" - class:hidden={idx < firstVisibleRow || idx > lastVisibleRow} + {#each fields as field} +
sortBy(schema[field])} + > +
{getDisplayName(schema[field])}
+ {#if schema[field]?.autocolumn} + - {#if idx >= firstVisibleRow && idx <= lastVisibleRow} - {#if showEditColumn} - -
- toggleSelectRow(row)} - onEdit={e => editRow(e, row)} - {allowSelectRows} - {allowEditRows} - /> -
- - {/if} - {#each fields as field} -
- {/each} - {/if} - - {/each} - {:else} - - {#if showEditColumn} - + + + {/if} + {#if sortColumn === field} + + {/if} + {#if allowEditColumns && schema[field]?.editable !== false} + editColumn(e, field)} + > + + + {/if} + + {/each} + + {/if} + {#if sortedRows?.length} + {#each sortedRows as row, idx} +
dispatch("click", row)} + on:click={() => toggleSelectRow(row)} + > + {#if showEditColumn} +
{ + toggleSelectRow(row) + e.stopPropagation() + }} + > + selectedRow._id === row._id + ) !== -1} + onEdit={e => editRow(e, row)} + {allowSelectRows} + {allowEditRows} + /> +
{/if} -
-
-
- {editColumnTitle || ""} -
-
sortBy(schema[field])} - > -
-
{getDisplayName(schema[field])}
- {#if schema[field]?.autocolumn} - - - - {/if} - {#if sortColumn === field} - - {/if} - {#if allowEditColumns && schema[field]?.editable !== false} - editColumn(e, field)} - > - - - {/if} -
-
-
- - - -
-
- {/if} - {#each fields as field} - - {/each} -
0}> -
- - - -
No rows found
-
-
-
- + {#each fields as field} +
+ + + +
+ {/each} + + {/each} + {:else} +
+
+ + + +
No rows found
+
+
+ {/if} {/if} diff --git a/packages/bbui/src/Tabs/Tabs.svelte b/packages/bbui/src/Tabs/Tabs.svelte index 40e02058c1..6930a6cdb5 100644 --- a/packages/bbui/src/Tabs/Tabs.svelte +++ b/packages/bbui/src/Tabs/Tabs.svelte @@ -68,7 +68,7 @@ }) function id() { - return "_" + Math.random().toString(36).substr(2, 9) + return "_" + Math.random().toString(36).slice(2, 9) } diff --git a/packages/bbui/src/index.js b/packages/bbui/src/index.js index d3bc11cf9d..2b16f32b84 100644 --- a/packages/bbui/src/index.js +++ b/packages/bbui/src/index.js @@ -60,6 +60,7 @@ export { default as StatusLight } from "./StatusLight/StatusLight.svelte" export { default as ColorPicker } from "./ColorPicker/ColorPicker.svelte" export { default as InlineAlert } from "./InlineAlert/InlineAlert.svelte" export { default as Banner } from "./Banner/Banner.svelte" +export { default as BannerDisplay } from "./Banner/BannerDisplay.svelte" export { default as MarkdownEditor } from "./Markdown/MarkdownEditor.svelte" export { default as MarkdownViewer } from "./Markdown/MarkdownViewer.svelte" export { default as RichTextField } from "./Form/RichTextField.svelte" @@ -84,6 +85,7 @@ export { default as clickOutside } from "./Actions/click_outside" // Stores export { notifications, createNotificationStore } from "./Stores/notifications" +export { banner } from "./Stores/banner" // Helpers export * as Helpers from "./helpers" diff --git a/packages/builder/cypress.json b/packages/builder/cypress.json index 0908f2c839..edad1f39d3 100644 --- a/packages/builder/cypress.json +++ b/packages/builder/cypress.json @@ -1,9 +1,11 @@ { - "baseUrl": "http://localhost:10001/builder/", - "video": true, + "baseUrl": "http://localhost:4100", + "video": false, "projectId": "bmbemn", "env": { - "PORT": "10001", - "JWT_SECRET": "test" + "PORT": "4100", + "WORKER_PORT": "4200", + "JWT_SECRET": "test", + "HOST_IP": "" } } diff --git a/packages/builder/cypress/integration/addMultiOptionDatatype.spec.js b/packages/builder/cypress/integration/addMultiOptionDatatype.spec.js index b3e0f413f8..e67f1eb2a2 100644 --- a/packages/builder/cypress/integration/addMultiOptionDatatype.spec.js +++ b/packages/builder/cypress/integration/addMultiOptionDatatype.spec.js @@ -1,14 +1,17 @@ -context("Add Multi-Option Datatype", () => { - before(() => { - cy.login() - cy.createTestApp() - }) +import filterTests from "../support/filterTests" - it("should create a new table, with data", () => { - cy.createTable("Multi Data") - cy.addColumn("Multi Data", "Test Data", "Multi-select", "1\n2\n3\n4\n5") - cy.addRowMultiValue(["1", "2", "3", "4", "5"]) - }) +filterTests(['all'], () => { + context("Add Multi-Option Datatype", () => { + before(() => { + cy.login() + cy.createTestApp() + }) + + it("should create a new table, with data", () => { + cy.createTable("Multi Data") + cy.addColumn("Multi Data", "Test Data", "Multi-select", "1\n2\n3\n4\n5") + cy.addRowMultiValue(["1", "2", "3", "4", "5"]) + }) it("should add form with multi select picker, containing 5 options", () => { cy.navigateToFrontend() @@ -39,6 +42,7 @@ context("Add Multi-Option Datatype", () => { cy.getComponent(componentId) .find(".spectrum-Picker-label") .contains("(5)") + }) }) }) }) diff --git a/packages/builder/cypress/integration/addRadioButtons.spec.js b/packages/builder/cypress/integration/addRadioButtons.spec.js index 68d0a74d55..9888b56086 100644 --- a/packages/builder/cypress/integration/addRadioButtons.spec.js +++ b/packages/builder/cypress/integration/addRadioButtons.spec.js @@ -1,35 +1,39 @@ -context("Add Radio Buttons", () => { - before(() => { - cy.login() - cy.createTestApp() - }) +import filterTests from "../support/filterTests" -it("should add Radio Buttons options picker on form, add data, and confirm", () => { - cy.navigateToFrontend() - cy.addComponent("Form", "Form") - cy.addComponent("Form", "Options Picker").then((componentId) => { - // Provide field setting - cy.get(`[data-cy="field-prop-control"]`).type("1") - // Open dropdown and select Radio buttons - cy.get(`[data-cy="optionsType-prop-control"]`).click().then(() => { - cy.get('.spectrum-Popover').contains('Radio buttons') - .wait(500) - .click() +filterTests(['all'], () => { + context("Add Radio Buttons", () => { + before(() => { + cy.login() + cy.createTestApp() }) - const radioButtonsTotal = 3 - // Add values and confirm total - addRadioButtonData(radioButtonsTotal) - cy.getComponent(componentId).find('[type="radio"]') - .should('have.length', radioButtonsTotal) - }) - }) - - const addRadioButtonData = (totalRadioButtons) => { - cy.get(`[data-cy="optionsSource-prop-control"]`).click().then(() => { - cy.get('.spectrum-Popover').contains('Custom') + + it("should add Radio Buttons options picker on form, add data, and confirm", () => { + cy.navigateToFrontend() + cy.addComponent("Form", "Form") + cy.addComponent("Form", "Options Picker").then((componentId) => { + // Provide field setting + cy.get(`[data-cy="field-prop-control"]`).type("1") + // Open dropdown and select Radio buttons + cy.get(`[data-cy="optionsType-prop-control"]`).click().then(() => { + cy.get('.spectrum-Popover').contains('Radio buttons') .wait(500) .click() + }) + const radioButtonsTotal = 3 + // Add values and confirm total + addRadioButtonData(radioButtonsTotal) + cy.getComponent(componentId).find('[type="radio"]') + .should('have.length', radioButtonsTotal) + }) }) - cy.addCustomSourceOptions(totalRadioButtons) - } + + const addRadioButtonData = (totalRadioButtons) => { + cy.get(`[data-cy="optionsSource-prop-control"]`).click().then(() => { + cy.get('.spectrum-Popover').contains('Custom') + .wait(500) + .click() + }) + cy.addCustomSourceOptions(totalRadioButtons) + } + }) }) diff --git a/packages/builder/cypress/integration/autoScreensUI.spec.js b/packages/builder/cypress/integration/autoScreensUI.spec.js new file mode 100644 index 0000000000..ab65e6561f --- /dev/null +++ b/packages/builder/cypress/integration/autoScreensUI.spec.js @@ -0,0 +1,51 @@ +import filterTests from "../support/filterTests" + +filterTests(['smoke', 'all'], () => { + context("Auto Screens UI", () => { + before(() => { + cy.login() + cy.createTestApp() + }) + + it("should generate internal table screens", () => { + // Create autogenerated screens from the internal table + cy.createAutogeneratedScreens(["Cypress Tests"]) + // Confirm screens have been auto generated + cy.get(".nav-items-container").contains("cypress-tests").click({ force: true }) + cy.get(".nav-items-container").should('contain', 'cypress-tests/:id') + .and('contain', 'cypress-tests/new/row') + }) + + it("should generate multiple internal table screens at once", () => { + // Create a second internal table + const initialTable = "Cypress Tests" + const secondTable = "Table Two" + cy.createTable(secondTable) + // Create autogenerated screens from the internal tables + cy.createAutogeneratedScreens([initialTable, secondTable]) + // Confirm screens have been auto generated + cy.get(".nav-items-container").contains("cypress-tests").click({ force: true }) + // Previously generated tables are suffixed with numbers - as expected + cy.get(".nav-items-container").should('contain', 'cypress-tests-2/:id') + .and('contain', 'cypress-tests-2/new/row') + cy.get(".nav-items-container").contains("table-two").click() + cy.get(".nav-items-container").should('contain', 'table-two/:id') + .and('contain', 'table-two/new/row') + }) + + if (Cypress.env("TEST_ENV")) { + it("should generate data source screens", () => { + // Using MySQL data source for testing this + const datasource = "MySQL" + // Select & configure MySQL data source + cy.selectExternalDatasource(datasource) + cy.addDatasourceConfig(datasource) + // Create autogenerated screens from a MySQL table - MySQL contains books table + cy.createAutogeneratedScreens(["books"]) + cy.get(".nav-items-container").contains("books").click() + cy.get(".nav-items-container").should('contain', 'books/:id') + .and('contain', 'books/new/row') + }) + } + }) +}) diff --git a/packages/builder/cypress/integration/changeAppIconAndColour.spec.js b/packages/builder/cypress/integration/changeAppIconAndColour.spec.js new file mode 100644 index 0000000000..92d03a102d --- /dev/null +++ b/packages/builder/cypress/integration/changeAppIconAndColour.spec.js @@ -0,0 +1,43 @@ +import filterTests from "../support/filterTests" + +filterTests(['all'], () => { + context("Change Application Icon and Colour", () => { + before(() => { + cy.login() + }) + + it("should change the icon and colour for an application", () => { + // Search for test application + cy.searchForApplication("Cypress Tests") + cy.get(".appTable") + .within(() => { + cy.get(".spectrum-Icon").eq(1).click() + }) + cy.get(".spectrum-Menu").contains("Edit icon").click() + // Select random icon + cy.get(".grid").within(() => { + cy.get(".icon-item").eq(Math.floor(Math.random() * 23) + 1).click() + }) + // Select random colour + cy.get(".fill").click() + cy.get(".colors").within(() => { + cy.get(".color").eq(Math.floor(Math.random() * 33) + 1).click() + }) + cy.intercept('**/applications/**').as('iconChange') + cy.get(".spectrum-Button").contains("Save").click({ force: true }) + cy.wait("@iconChange") + cy.get("@iconChange").its('response.statusCode') + .should('eq', 200) + cy.wait(1000) + // Confirm icon has changed from default + // Confirm colour has been applied - There is no default colour + cy.get(".appTable") + .within(() => { + cy.get('[aria-label]').eq(0).children() + .should('have.attr', 'xlink:href').and('not.contain', '#spectrum-icon-18-Apps') + cy.get(".title").children().children() + .should('have.attr', 'style').and('contains', 'color') + }) + }) + }) +}) diff --git a/packages/builder/cypress/integration/createApp.spec.js b/packages/builder/cypress/integration/createApp.spec.js index 34f152b540..273683aec8 100644 --- a/packages/builder/cypress/integration/createApp.spec.js +++ b/packages/builder/cypress/integration/createApp.spec.js @@ -1,8 +1,12 @@ -context("Create an Application", () => { - it("should create a new application", () => { - cy.login() - cy.createTestApp() - cy.visit(`localhost:${Cypress.env("PORT")}/builder`) - cy.contains("Cypress Tests").should("exist") - }) +import filterTests from '../support/filterTests' + +filterTests(['smoke', 'all'], () => { + context("Create an Application", () => { + it("should create a new application", () => { + cy.login() + cy.createTestApp() + cy.visit(`${Cypress.config().baseUrl}/builder`) + cy.contains("Cypress Tests").should("exist") + }) + }) }) diff --git a/packages/builder/cypress/integration/createAutomation.spec.js b/packages/builder/cypress/integration/createAutomation.spec.js index efd3c7d023..b9355f7faf 100644 --- a/packages/builder/cypress/integration/createAutomation.spec.js +++ b/packages/builder/cypress/integration/createAutomation.spec.js @@ -1,66 +1,69 @@ -context("Create a automation", () => { - before(() => { - cy.login() - cy.createTestApp() - }) +import filterTests from "../support/filterTests" - // https://on.cypress.io/interacting-with-elements - it("should create a automation", () => { - cy.createTestTableWithData() - cy.wait(2000) - cy.contains("Automate").click() - cy.get("[data-cy='new-screen'] > .spectrum-Icon").click() - cy.get(".modal-inner-wrapper").within(() => { - cy.get("input").type("Add Row") - cy.contains("Row Created").click({ force: true }) - cy.wait(500) - cy.get(".spectrum-Button--cta").click() +filterTests(['smoke', 'all'], () => { + context("Create a automation", () => { + before(() => { + cy.login() + cy.createTestApp() }) - // Setup trigger - cy.contains("Setup").click() - cy.get(".spectrum-Picker-label").click() - cy.wait(500) - cy.contains("dog").click() - cy.wait(2000) - // Create action - cy.get(".block > .spectrum-Icon").click() - cy.get(".modal-inner-wrapper").within(() => { - cy.wait(1000) - cy.contains("Create Row").trigger('mouseover').click().click() - cy.get(".spectrum-Button--cta").click() - }) - cy.contains("Setup").click() - cy.get(".spectrum-Picker-label").click() - cy.contains("dog").click() - cy.get(".spectrum-Textfield-input") - .first() - .type("{{ trigger.row.name }}", { parseSpecialCharSequences: false }) - cy.get(".spectrum-Textfield-input") - .eq(1) - .type("11") - cy.contains("Finish and test automation").click() + it("should create a automation", () => { + cy.createTestTableWithData() + cy.wait(2000) + cy.contains("Automate").click() + cy.get("[data-cy='new-screen'] > .spectrum-Icon").click() + cy.get(".modal-inner-wrapper").within(() => { + cy.get("input").type("Add Row") + cy.contains("Row Created").click({ force: true }) + cy.wait(500) + cy.get(".spectrum-Button--cta").click() + }) - cy.get(".modal-inner-wrapper").within(() => { - cy.wait(1000) + // Setup trigger + cy.contains("Setup").click() cy.get(".spectrum-Picker-label").click() + cy.wait(500) + cy.contains("dog").click() + cy.wait(2000) + // Create action + cy.get('[aria-label="AddCircle"]').eq(1).click() + cy.get(".modal-inner-wrapper").within(() => { + cy.wait(1000) + cy.contains("Create Row").trigger('mouseover').click().click() + cy.get(".spectrum-Button--cta").click() + }) + cy.contains("Setup").click() + cy.get(".spectrum-Picker-label").eq(1).click() cy.contains("dog").click() - cy.wait(1000) cy.get(".spectrum-Textfield-input") - .first() - .type("automationGoodboy") + .first() + .type("{{ trigger.row.name }}", { parseSpecialCharSequences: false }) cy.get(".spectrum-Textfield-input") .eq(1) .type("11") - cy.get(".spectrum-Textfield-input") - .eq(2) - .type("123456") - cy.get(".spectrum-Textfield-input") - .eq(3) - .type("123456") - cy.contains("Test").click() + cy.contains("Finish and test automation").click() + + cy.get(".modal-inner-wrapper").within(() => { + cy.wait(1000) + cy.get(".spectrum-Picker-label").click() + cy.contains("dog").click() + cy.wait(1000) + cy.get(".spectrum-Textfield-input") + .first() + .type("automationGoodboy") + cy.get(".spectrum-Textfield-input") + .eq(1) + .type("11") + cy.get(".spectrum-Textfield-input") + .eq(2) + .type("123456") + cy.get(".spectrum-Textfield-input") + .eq(3) + .type("123456") + cy.contains("Test").click() + }) + cy.contains("Data").click() + cy.contains("automationGoodboy") }) - cy.contains("Data").click() - cy.contains("automationGoodboy") }) }) diff --git a/packages/builder/cypress/integration/createBinding.spec.js b/packages/builder/cypress/integration/createBinding.spec.js index 3f85526b11..8bf1ec8ea4 100644 --- a/packages/builder/cypress/integration/createBinding.spec.js +++ b/packages/builder/cypress/integration/createBinding.spec.js @@ -1,58 +1,62 @@ -context("Create Bindings", () => { - before(() => { - cy.login() - cy.createTestApp() - cy.navigateToFrontend() - }) +import filterTests from "../support/filterTests" - it("should add a current user binding", () => { - cy.addComponent("Elements", "Paragraph").then(() => { - addSettingBinding("text", "Current User._id") +filterTests(['smoke', 'all'], () => { + context("Create Bindings", () => { + before(() => { + cy.login() + cy.createTestApp() + cy.navigateToFrontend() + }) + + it("should add a current user binding", () => { + cy.addComponent("Elements", "Paragraph").then(() => { + addSettingBinding("text", "Current User._id") + }) + }) + + it("should handle an invalid binding", () => { + cy.addComponent("Elements", "Paragraph").then(componentId => { + // Cypress needs to escape curly brackets + cy.get("[data-cy=setting-text] input") + .type("{{}{{}{{} Current User._id {}}{}}") + .blur() + cy.getComponent(componentId).should("have.text", "{{{ [user].[_id] }}") + }) + }) + + it("should add a URL param binding", () => { + const paramName = "foo" + cy.createScreen("Test Param", `/test/:${paramName}`) + cy.addComponent("Elements", "Paragraph").then(componentId => { + addSettingBinding("text", `URL.${paramName}`) + // The builder preview pages don't have a real URL, so all we can do + // is check that we were able to bind to the property, and that the + // component exists on the page + cy.getComponent(componentId).should("have.text", "New Paragraph") + }) + }) + + it("should add a binding with a handlebars helper", () => { + cy.addComponent("Elements", "Paragraph").then(componentId => { + // Cypress needs to escape curly brackets + cy.get("[data-cy=setting-text] input") + .type("{{}{{} add 1 2 {}}{}}") + .blur() + cy.getComponent(componentId).should("have.text", "3") + }) }) }) - it("should handle an invalid binding", () => { - cy.addComponent("Elements", "Paragraph").then(componentId => { - // Cypress needs to escape curly brackets - cy.get("[data-cy=setting-text] input") - .type("{{}{{}{{} Current User._id {}}{}}") - .blur() - cy.getComponent(componentId).should("have.text", "{{{ [user].[_id] }}") + const addSettingBinding = (setting, bindingText, clickOption = true) => { + cy.get(`[data-cy="setting-${setting}"] [data-cy=text-binding-button]`).click() + cy.get(".drawer").within(() => { + if (clickOption) { + cy.contains(bindingText).click() + cy.get("textarea").should("have.value", `{{ ${bindingText} }}`) + } else { + cy.get("textarea").type(bindingText) + } + cy.contains("Save").click() }) - }) - - it("should add a URL param binding", () => { - const paramName = "foo" - cy.createScreen("Test Param", `/test/:${paramName}`) - cy.addComponent("Elements", "Paragraph").then(componentId => { - addSettingBinding("text", `URL.${paramName}`) - // The builder preview pages don't have a real URL, so all we can do - // is check that we were able to bind to the property, and that the - // component exists on the page - cy.getComponent(componentId).should("have.text", "New Paragraph") - }) - }) - - it("should add a binding with a handlebars helper", () => { - cy.addComponent("Elements", "Paragraph").then(componentId => { - // Cypress needs to escape curly brackets - cy.get("[data-cy=setting-text] input") - .type("{{}{{} add 1 2 {}}{}}") - .blur() - cy.getComponent(componentId).should("have.text", "3") - }) - }) + } }) - -const addSettingBinding = (setting, bindingText, clickOption = true) => { - cy.get(`[data-cy="setting-${setting}"] [data-cy=text-binding-button]`).click() - cy.get(".drawer").within(() => { - if (clickOption) { - cy.contains(bindingText).click() - cy.get("textarea").should("have.value", `{{ ${bindingText} }}`) - } else { - cy.get("textarea").type(bindingText) - } - cy.contains("Save").click() - }) -} diff --git a/packages/builder/cypress/integration/createComponents.spec.js b/packages/builder/cypress/integration/createComponents.spec.js index 93867e0f1f..e13439d9c6 100644 --- a/packages/builder/cypress/integration/createComponents.spec.js +++ b/packages/builder/cypress/integration/createComponents.spec.js @@ -1,92 +1,97 @@ // TODO for now components are skipped, might not be good to keep doing this -xcontext("Create Components", () => { - let headlineId - before(() => { - cy.login() - cy.createTestApp() - cy.createTable("dog") - cy.addColumn("dog", "name", "Text") - cy.addColumn("dog", "age", "Number") - cy.addColumn("dog", "type", "Options") - cy.navigateToFrontend() - }) +import filterTests from "../support/filterTests" - it("should add a container", () => { - cy.addComponent(null, "Container").then(componentId => { - cy.getComponent(componentId).should("exist") +filterTests(['all'], () => { + xcontext("Create Components", () => { + let headlineId + + before(() => { + cy.login() + cy.createTestApp() + cy.createTable("dog") + cy.addColumn("dog", "name", "Text") + cy.addColumn("dog", "age", "Number") + cy.addColumn("dog", "type", "Options") + cy.navigateToFrontend() }) - }) - it("should add a headline", () => { - cy.addComponent("Elements", "Headline").then(componentId => { - headlineId = componentId - cy.getComponent(headlineId).should("exist") + it("should add a container", () => { + cy.addComponent(null, "Container").then(componentId => { + cy.getComponent(componentId).should("exist") + }) }) - }) - it("should change the text of the headline", () => { - const text = "Lorem ipsum dolor sit amet." - cy.get("[data-cy=Settings]").click() - cy.get("[data-cy=setting-text] input") - .type(text) - .blur() - cy.getComponent(headlineId).should("have.text", text) - }) + it("should add a headline", () => { + cy.addComponent("Elements", "Headline").then(componentId => { + headlineId = componentId + cy.getComponent(headlineId).should("exist") + }) + }) - it("should change the size of the headline", () => { - cy.get("[data-cy=Design]").click() - cy.contains("Typography").click() - cy.get("[data-cy=font-size-prop-control]").click() - cy.contains("60px").click() - cy.getComponent(headlineId).should("have.css", "font-size", "60px") - }) - - it("should create a form and reset to match schema", () => { - cy.addComponent("Form", "Form").then(() => { + it("should change the text of the headline", () => { + const text = "Lorem ipsum dolor sit amet." cy.get("[data-cy=Settings]").click() - cy.get("[data-cy=setting-dataSource]") - .contains("Choose option") - .click() - cy.get(".dropdown") - .contains("dog") - .click() - cy.addComponent("Form", "Field Group").then(fieldGroupId => { + cy.get("[data-cy=setting-text] input") + .type(text) + .blur() + cy.getComponent(headlineId).should("have.text", text) + }) + + it("should change the size of the headline", () => { + cy.get("[data-cy=Design]").click() + cy.contains("Typography").click() + cy.get("[data-cy=font-size-prop-control]").click() + cy.contains("60px").click() + cy.getComponent(headlineId).should("have.css", "font-size", "60px") + }) + + it("should create a form and reset to match schema", () => { + cy.addComponent("Form", "Form").then(() => { cy.get("[data-cy=Settings]").click() - cy.contains("Update Form Fields").click() - cy.get(".modal") - .get("button.primary") + cy.get("[data-cy=setting-dataSource]") + .contains("Choose option") .click() - cy.getComponent(fieldGroupId).within(() => { - cy.contains("name").should("exist") - cy.contains("age").should("exist") - cy.contains("type").should("exist") + cy.get(".dropdown") + .contains("dog") + .click() + cy.addComponent("Form", "Field Group").then(fieldGroupId => { + cy.get("[data-cy=Settings]").click() + cy.contains("Update Form Fields").click() + cy.get(".modal") + .get("button.primary") + .click() + cy.getComponent(fieldGroupId).within(() => { + cy.contains("name").should("exist") + cy.contains("age").should("exist") + cy.contains("type").should("exist") + }) + cy.getComponent(fieldGroupId) + .find("input") + .should("have.length", 2) + cy.getComponent(fieldGroupId) + .find(".spectrum-Picker") + .should("have.length", 1) }) - cy.getComponent(fieldGroupId) - .find("input") - .should("have.length", 2) - cy.getComponent(fieldGroupId) - .find(".spectrum-Picker") - .should("have.length", 1) }) }) - }) - it("deletes a component", () => { - cy.addComponent("Elements", "Paragraph").then(componentId => { - cy.get("[data-cy=setting-_instanceName] input") - .type(componentId) - .blur() - cy.get(".ui-nav ul .nav-item.selected .ri-more-line").click({ - force: true, + it("deletes a component", () => { + cy.addComponent("Elements", "Paragraph").then(componentId => { + cy.get("[data-cy=setting-_instanceName] input") + .type(componentId) + .blur() + cy.get(".ui-nav ul .nav-item.selected .ri-more-line").click({ + force: true, + }) + cy.get(".dropdown-container") + .contains("Delete") + .click() + cy.get(".modal") + .contains("Delete Component") + .click() + cy.getComponent(componentId).should("not.exist") }) - cy.get(".dropdown-container") - .contains("Delete") - .click() - cy.get(".modal") - .contains("Delete Component") - .click() - cy.getComponent(componentId).should("not.exist") }) }) }) diff --git a/packages/builder/cypress/integration/createScreen.js b/packages/builder/cypress/integration/createScreen.js index 26c7cdd846..ada68d82dc 100644 --- a/packages/builder/cypress/integration/createScreen.js +++ b/packages/builder/cypress/integration/createScreen.js @@ -1,21 +1,25 @@ -context("Screen Tests", () => { - before(() => { - cy.login() - cy.createTestApp() - cy.navigateToFrontend() - }) +import filterTests from "../support/filterTests" - it("Should successfully create a screen", () => { - cy.createScreen("Test Screen", "/test") - cy.get(".nav-items-container").within(() => { - cy.contains("/test").should("exist") +filterTests(["smoke", "all"], () => { + context("Screen Tests", () => { + before(() => { + cy.login() + cy.createTestApp() + cy.navigateToFrontend() }) - }) - it("Should update the url", () => { - cy.createScreen("Test Screen", "test with spaces") - cy.get(".nav-items-container").within(() => { - cy.contains("/test-with-spaces").should("exist") + it("Should successfully create a screen", () => { + cy.createScreen("Test Screen", "/test") + cy.get(".nav-items-container").within(() => { + cy.contains("/test").should("exist") + }) + }) + + it("Should update the url", () => { + cy.createScreen("Test Screen", "test with spaces") + cy.get(".nav-items-container").within(() => { + cy.contains("/test-with-spaces").should("exist") + }) }) }) }) diff --git a/packages/builder/cypress/integration/createTable.spec.js b/packages/builder/cypress/integration/createTable.spec.js index 20ec919b57..81b7c2f045 100644 --- a/packages/builder/cypress/integration/createTable.spec.js +++ b/packages/builder/cypress/integration/createTable.spec.js @@ -1,74 +1,113 @@ -context("Create a Table", () => { - before(() => { - cy.login() - cy.createTestApp() - }) +import filterTests from "../support/filterTests" - it("should create a new Table", () => { - cy.createTable("dog") - cy.wait(1000) - // Check if Table exists - cy.get(".table-title h1").should("have.text", "dog") - }) +filterTests(["smoke", "all"], () => { + context("Create a Table", () => { + before(() => { + cy.login() + cy.createTestApp() + }) - it("adds a new column to the table", () => { - cy.addColumn("dog", "name", "Text") - cy.contains("name").should("be.visible") - }) + it("should create a new Table", () => { + cy.createTable("dog") + cy.wait(1000) + // Check if Table exists + cy.get(".table-title h1").should("have.text", "dog") + }) - it("creates a row in the table", () => { - cy.addRow(["Rover"]) - cy.contains("Rover").should("be.visible") - }) + it("adds a new column to the table", () => { + cy.addColumn("dog", "name", "Text") + cy.contains("name").should("be.visible") + }) - it("updates a column on the table", () => { - cy.get(".title").click() - cy.get(".spectrum-Table-editIcon > use").click() - cy.get("input").eq(1).type("updated", { force: true }) - // Unset table display column - cy.get(".spectrum-Switch-input").eq(1).click() - cy.contains("Save Column").click() - cy.contains("nameupdated ").should("contain", "nameupdated") - }) + it("creates a row in the table", () => { + cy.addRow(["Rover"]) + cy.contains("Rover").should("be.visible") + }) - - it("edits a row", () => { - cy.contains("button", "Edit").click({ force: true }) - cy.wait(1000) - cy.get(".spectrum-Modal input").clear() - cy.get(".spectrum-Modal input").type("Updated") - cy.contains("Save").click() - cy.contains("Updated").should("have.text", "Updated") - }) - - it("deletes a row", () => { - cy.get(".spectrum-Checkbox-input").check({ force: true }) - cy.contains("Delete 1 row(s)").click() - cy.get(".spectrum-Modal").contains("Delete").click() - cy.contains("RoverUpdated").should("not.exist") - }) + it("updates a column on the table", () => { + cy.get(".title").click() + cy.get(".spectrum-Table-editIcon > use").click() + cy.get(".modal-inner-wrapper").within(() => { - it("deletes a column", () => { - cy.get(".title").click() - cy.get(".spectrum-Table-editIcon > use").click() - cy.contains("Delete").click() - cy.wait(50) - cy.get(`[data-cy="delete-column-confirm"]`).type("nameupdated") - cy.contains("Delete Column").click() - cy.contains("nameupdated").should("not.exist") - }) - - it("deletes a table", () => { - cy.get(".nav-item") - .contains("dog") - .parents(".nav-item") - .first() - .within(() => { - cy.get(".actions .spectrum-Icon").click({ force: true }) + cy.get("input").eq(0).type("updated", { force: true }) + // Unset table display column + cy.get(".spectrum-Switch-input").eq(1).click() + cy.contains("Save Column").click() }) - cy.get(".spectrum-Menu > :nth-child(2)").click() - cy.get(`[data-cy="delete-table-confirm"]`).type("dog") - cy.contains("Delete Table").click() - cy.contains("dog").should("not.exist") + cy.contains("nameupdated ").should("contain", "nameupdated") + }) + + it("edits a row", () => { + cy.contains("button", "Edit").click({ force: true }) + cy.wait(1000) + cy.get(".spectrum-Modal input").clear() + cy.get(".spectrum-Modal input").type("Updated") + cy.contains("Save").click() + cy.contains("Updated").should("have.text", "Updated") + }) + + it("deletes a row", () => { + cy.get(".spectrum-Checkbox-input").check({ force: true }) + cy.contains("Delete 1 row(s)").click() + cy.get(".spectrum-Modal").contains("Delete").click() + cy.contains("RoverUpdated").should("not.exist") + }) + + if (Cypress.env("TEST_ENV")) { + // No Pagination in CI - Test env only for the next two tests + it("Adds 15 rows and checks pagination", () => { + // 10 rows per page, 15 rows should create 2 pages within table + const totalRows = 16 + for (let i = 1; i < totalRows; i++) { + cy.addRow([i]) + } + cy.wait(1000) + cy.get(".spectrum-Pagination").within(() => { + cy.get(".spectrum-ActionButton").eq(1).click() + }) + cy.get(".spectrum-Pagination").within(() => { + cy.get(".spectrum-Body--secondary").contains("Page 2") + }) + }) + + it("Deletes rows and checks pagination", () => { + // Delete rows, removing second page of rows from table + const deleteRows = 5 + cy.get(".spectrum-Checkbox-input").check({ force: true }) + cy.get(".spectrum-Table") + cy.contains("Delete 5 row(s)").click() + cy.get(".spectrum-Modal").contains("Delete").click() + cy.wait(1000) + + // Confirm table only has one page + cy.get(".spectrum-Pagination").within(() => { + cy.get(".spectrum-ActionButton").eq(1).should("not.be.enabled") + }) + }) + } + + it("deletes a column", () => { + const columnName = "nameupdated" + cy.get(".title").click() + cy.get(".spectrum-Table-editIcon > use").click() + cy.contains("Delete").click() + cy.get('[data-cy="delete-column-confirm"]').type(columnName) + cy.contains("Delete Column").click() + cy.contains("nameupdated").should("not.exist") + }) + + it("deletes a table", () => { + cy.get(".nav-item") + .contains("dog") + .parents(".nav-item") + .first() + .within(() => { + cy.get(".actions .spectrum-Icon").click({ force: true }) + }) + cy.get(".spectrum-Menu > :nth-child(2)").click() + cy.get('[data-cy="delete-table-confirm"]').type("dog") + cy.contains("Delete Table").click() + cy.contains("dog").should("not.exist") + }) }) }) diff --git a/packages/builder/cypress/integration/createUser.spec.js b/packages/builder/cypress/integration/createUser.spec.js deleted file mode 100644 index 18ae8a16a0..0000000000 --- a/packages/builder/cypress/integration/createUser.spec.js +++ /dev/null @@ -1,10 +0,0 @@ -context("Create a User", () => { - before(() => { - cy.login() - }) - - it("should create a user", () => { - cy.createUser("bbuser@test.com") - cy.contains("bbuser").should("be.visible") - }) -}) diff --git a/packages/builder/cypress/integration/createUserAndRoles.spec.js b/packages/builder/cypress/integration/createUserAndRoles.spec.js new file mode 100644 index 0000000000..ce6d370187 --- /dev/null +++ b/packages/builder/cypress/integration/createUserAndRoles.spec.js @@ -0,0 +1,180 @@ +import filterTests from "../support/filterTests" + +filterTests(["smoke", "all"], () => { + context("Create a User and Assign Roles", () => { + before(() => { + cy.login() + }) + + it("should create a user", () => { + cy.createUser("bbuser@test.com") + cy.get(".spectrum-Table").should("contain", "bbuser") + }) + + it("should confirm there is No Access for a New User", () => { + // Click into the user + cy.contains("bbuser").click() + cy.wait(500) + // Get No Access table - Confirm it has apps in it + cy.get(".spectrum-Table").eq(1).should("not.contain", "No rows found") + // Get Configure Roles table - Confirm it has no apps + cy.get(".spectrum-Table").eq(0).contains("No rows found") + }) + + it("should assign role types", () => { + // 3 apps minimum required - to assign an app to each role type + cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`) + .its("body") + .then(val => { + if (val.length < 3) { + for (let i = 1; i < 3; i++) { + const uuid = () => Cypress._.random(0, 1e6) + const name = uuid() + cy.createApp(name) + } + } + }) + // Navigate back to the user + cy.visit(`${Cypress.config().baseUrl}/builder`) + cy.wait(500) + cy.get(".spectrum-SideNav").contains("Users").click() + cy.wait(500) + cy.get(".spectrum-Table").contains("bbuser").click() + cy.wait(1000) + for (let i = 0; i < 3; i++) { + cy.get(".spectrum-Table") + .eq(1) + .find(".spectrum-Table-row") + .eq(0) + .find(".spectrum-Table-cell") + .eq(0) + .click() + cy.wait(500) + cy.get(".spectrum-Dialog-grid") + .contains("Choose an option") + .click() + .then(() => { + cy.wait(1000) + if (i == 0) { + cy.get(".spectrum-Popover").contains("Admin").click() + } + if (i == 1) { + cy.get(".spectrum-Popover").contains("Power").click() + } + if (i == 2) { + cy.get(".spectrum-Popover").contains("Basic").click() + } + cy.wait(1000) + cy.get(".spectrum-Button") + .contains("Update role") + .click({ force: true }) + }) + } + // Confirm roles exist within Configure roles table + cy.wait(2000) + cy.get(".spectrum-Table") + .eq(0) + .within(assginedRoles => { + expect(assginedRoles).to.contain("Admin") + expect(assginedRoles).to.contain("Power") + expect(assginedRoles).to.contain("Basic") + }) + }) + + it("should unassign role types", () => { + // Set each app within Configure roles table to 'No Access' + cy.get(".spectrum-Table") + .eq(0) + .find(".spectrum-Table-row") + .its("length") + .then(len => { + for (let i = 0; i < len; i++) { + cy.get(".spectrum-Table") + .eq(0) + .find(".spectrum-Table-row") + .eq(0) + .find(".spectrum-Table-cell") + .eq(0) + .click() + .then(() => { + cy.get(".spectrum-Picker").eq(1).click({ force: true }) + cy.wait(500) + cy.get(".spectrum-Popover").contains("No Access").click() + }) + cy.get(".spectrum-Button") + .contains("Update role") + .click({ force: true }) + cy.wait(1000) + } + }) + // Confirm Configure roles table no longer has any apps in it + cy.get(".spectrum-Table").eq(0).contains("No rows found") + }) + + it("should enable Developer access", () => { + // Enable Developer access + cy.get(".field") + .eq(4) + .within(() => { + cy.get(".spectrum-Switch-input").click({ force: true }) + }) + // No Access table should now be empty + cy.get(".container") + .contains("No Access") + .parent() + .within(() => { + cy.get(".spectrum-Table").contains("No rows found") + }) + + // Each app within Configure roles should have Admin access + cy.get(".spectrum-Table") + .eq(0) + .find(".spectrum-Table-row") + .its("length") + .then(len => { + for (let i = 0; i < len; i++) { + cy.get(".spectrum-Table") + .eq(0) + .find(".spectrum-Table-row") + .eq(i) + .contains("Admin") + cy.wait(500) + } + }) + }) + + it("should disable Developer access", () => { + // Disable Developer access + cy.get(".field") + .eq(4) + .within(() => { + cy.get(".spectrum-Switch-input").click({ force: true }) + }) + // Configure roles table should now be empty + cy.get(".container") + .contains("Configure roles") + .parent() + .within(() => { + cy.get(".spectrum-Table").contains("No rows found") + }) + }) + + it("should delete a user", () => { + // Click Delete user button + cy.get(".spectrum-Button") + .contains("Delete user") + .click({ force: true }) + .then(() => { + // Confirm deletion within modal + cy.wait(500) + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Button") + .contains("Delete user") + .click({ force: true }) + cy.wait(4000) + }) + }) + cy.get(".spectrum-Table").should("not.have.text", "bbuser") + }) + }) +}) diff --git a/packages/builder/cypress/integration/createView.spec.js b/packages/builder/cypress/integration/createView.spec.js index e82ab67c0d..6b06cde0bc 100644 --- a/packages/builder/cypress/integration/createView.spec.js +++ b/packages/builder/cypress/integration/createView.spec.js @@ -1,152 +1,156 @@ -context("Create a View", () => { - before(() => { - cy.login() - cy.createTestApp() - cy.createTable("data") - cy.addColumn("data", "group", "Text") - cy.addColumn("data", "age", "Number") - cy.addColumn("data", "rating", "Number") +import filterTests from "../support/filterTests" - // 6 Rows - cy.addRow(["Students", 25, 1]) - cy.addRow(["Students", 20, 3]) - cy.addRow(["Students", 18, 6]) - cy.addRow(["Students", 25, 2]) - cy.addRow(["Teachers", 49, 5]) - cy.addRow(["Teachers", 36, 3]) - }) +filterTests(['smoke', 'all'], () => { + context("Create a View", () => { + before(() => { + cy.login() + cy.createTestApp() + cy.createTable("data") + cy.addColumn("data", "group", "Text") + cy.addColumn("data", "age", "Number") + cy.addColumn("data", "rating", "Number") - it("creates a view", () => { - cy.contains("Create view").click() - cy.get(".modal-inner-wrapper").within(() => { - cy.get("input").type("Test View") - cy.get("button").contains("Create View").click({ force: true }) + // 6 Rows + cy.addRow(["Students", 25, 1]) + cy.addRow(["Students", 20, 3]) + cy.addRow(["Students", 18, 6]) + cy.addRow(["Students", 25, 2]) + cy.addRow(["Teachers", 49, 5]) + cy.addRow(["Teachers", 36, 3]) }) - cy.get(".table-title h1").contains("Test View") - cy.get(".title").then($headers => { - expect($headers).to.have.length(3) - const headers = Array.from($headers).map(header => - header.textContent.trim() - ) - expect(removeSpacing(headers)).to.deep.eq(["group", "age", "rating"]) + + it("creates a view", () => { + cy.contains("Create view").click() + cy.get(".modal-inner-wrapper").within(() => { + cy.get("input").type("Test View") + cy.get("button").contains("Create View").click({ force: true }) + }) + cy.get(".table-title h1").contains("Test View") + cy.get(".title").then($headers => { + expect($headers).to.have.length(3) + const headers = Array.from($headers).map(header => + header.textContent.trim() + ) + expect(removeSpacing(headers)).to.deep.eq(["group", "age", "rating"]) + }) + }) + + it("filters the view by age over 10", () => { + cy.contains("Filter").click() + cy.contains("Add Filter").click() + + cy.get(".modal-inner-wrapper").within(() => { + cy.get(".spectrum-Picker-label").eq(0).click() + cy.contains("age").click({ force: true }) + + cy.get(".spectrum-Picker-label").eq(1).click() + cy.contains("More Than").click({ force: true }) + + cy.get("input").type(18) + cy.contains("Save").click() + }) + + cy.get(".spectrum-Table-row").get($values => { + expect($values).to.have.length(5) + }) + }) + + it("creates a stats calculation view based on age", () => { + cy.wait(1000) + cy.contains("Calculate").click() + cy.get(".modal-inner-wrapper").within(() => { + cy.get(".spectrum-Picker-label").eq(0).click() + cy.contains("Statistics").click() + + cy.get(".spectrum-Picker-label").eq(1).click() + cy.contains("age").click({ force: true }) + + cy.get(".spectrum-Button").contains("Save").click({ force: true }) + }) + cy.wait(1000) + + cy.get(".title").then($headers => { + expect($headers).to.have.length(7) + const headers = Array.from($headers).map(header => + header.textContent.trim() + ) + expect(removeSpacing(headers)).to.deep.eq([ + "field", + "sum", + "min", + "max", + "count", + "sumsqr", + "avg", + ]) + }) + cy.get(".spectrum-Table-cell").then($values => { + let values = Array.from($values).map(header => header.textContent.trim()) + expect(values).to.deep.eq(["age", "155", "20", "49", "5", "5347", "31"]) + }) + }) + + it("groups the view by group", () => { + cy.contains("Group by").click() + cy.get(".modal-inner-wrapper").within(() => { + cy.get(".spectrum-Picker-label").eq(0).click() + cy.contains("group").click() + cy.contains("Save").click() + }) + cy.wait(1000) + cy.contains("Students").should("be.visible") + cy.contains("Teachers").should("be.visible") + + cy.get(".spectrum-Table-cell").then($values => { + let values = Array.from($values).map(header => header.textContent.trim()) + expect(values).to.deep.eq([ + "Students", + "70", + "20", + "25", + "3", + "1650", + "23.333333333333332", + "Teachers", + "85", + "36", + "49", + "2", + "3697", + "42.5", + ]) + }) + }) + + it("renames a view", () => { + cy.contains(".nav-item", "Test View") + .find(".actions .icon") + .click({ force: true }) + cy.get(".spectrum-Menu-itemLabel").contains("Edit").click() + cy.get(".modal-inner-wrapper").within(() => { + cy.get("input").type(" Updated") + cy.contains("Save").click() + }) + cy.wait(1000) + cy.contains("Test View Updated").should("be.visible") + }) + + it("deletes a view", () => { + cy.contains(".nav-item", "Test View Updated") + .find(".actions .icon") + .click({ force: true }) + cy.contains("Delete").click() + cy.contains("Delete View").click() + cy.wait(500) + cy.contains("TestView Updated").should("not.exist") }) }) - it("filters the view by age over 10", () => { - cy.contains("Filter").click() - cy.contains("Add Filter").click() - - cy.get(".modal-inner-wrapper").within(() => { - cy.get(".spectrum-Picker-label").eq(0).click() - cy.contains("age").click({ force: true }) - - cy.get(".spectrum-Picker-label").eq(1).click() - cy.contains("More Than").click({ force: true }) - - cy.get("input").type(18) - cy.contains("Save").click() - }) - - cy.get(".spectrum-Table-row").get($values => { - expect($values).to.have.length(5) - }) - }) - - it("creates a stats calculation view based on age", () => { - cy.wait(1000) - cy.contains("Calculate").click() - cy.get(".modal-inner-wrapper").within(() => { - cy.get(".spectrum-Picker-label").eq(0).click() - cy.contains("Statistics").click() - - cy.get(".spectrum-Picker-label").eq(1).click() - cy.contains("age").click({ force: true }) - - cy.get(".spectrum-Button").contains("Save").click({ force: true }) - }) - cy.wait(1000) - - cy.get(".title").then($headers => { - expect($headers).to.have.length(7) - const headers = Array.from($headers).map(header => - header.textContent.trim() - ) - expect(removeSpacing(headers)).to.deep.eq([ - "field", - "sum", - "min", - "max", - "count", - "sumsqr", - "avg", - ]) - }) - cy.get(".spectrum-Table-cell").then($values => { - let values = Array.from($values).map(header => header.textContent.trim()) - expect(values).to.deep.eq(["age", "155", "20", "49", "5", "5347", "31"]) - }) - }) - - it("groups the view by group", () => { - cy.contains("Group by").click() - cy.get(".modal-inner-wrapper").within(() => { - cy.get(".spectrum-Picker-label").eq(0).click() - cy.contains("group").click() - cy.contains("Save").click() - }) - cy.wait(1000) - cy.contains("Students").should("be.visible") - cy.contains("Teachers").should("be.visible") - - cy.get(".spectrum-Table-cell").then($values => { - let values = Array.from($values).map(header => header.textContent.trim()) - expect(values).to.deep.eq([ - "Students", - "70", - "20", - "25", - "3", - "1650", - "23.333333333333332", - "Teachers", - "85", - "36", - "49", - "2", - "3697", - "42.5", - ]) - }) - }) - - it("renames a view", () => { - cy.contains(".nav-item", "Test View") - .find(".actions .icon") - .click({ force: true }) - cy.get(".spectrum-Menu-itemLabel").contains("Edit").click() - cy.get(".modal-inner-wrapper").within(() => { - cy.get("input").type(" Updated") - cy.contains("Save").click() - }) - cy.wait(1000) - cy.contains("Test View Updated").should("be.visible") - }) - - it("deletes a view", () => { - cy.contains(".nav-item", "Test View Updated") - .find(".actions .icon") - .click({ force: true }) - cy.contains("Delete").click() - cy.contains("Delete View").click() - cy.wait(500) - cy.contains("TestView Updated").should("not.exist") - }) -}) - -function removeSpacing(headers) { - let newHeaders = [] - for (let header of headers) { - newHeaders.push(header.replace(/\s\s+/g, " ")) + function removeSpacing(headers) { + let newHeaders = [] + for (let header of headers) { + newHeaders.push(header.replace(/\s\s+/g, " ")) + } + return newHeaders } - return newHeaders -} +}) diff --git a/packages/builder/cypress/integration/customThemingProperties.spec.js b/packages/builder/cypress/integration/customThemingProperties.spec.js index 5b7922bde7..ed3478ca67 100644 --- a/packages/builder/cypress/integration/customThemingProperties.spec.js +++ b/packages/builder/cypress/integration/customThemingProperties.spec.js @@ -1,84 +1,87 @@ -xcontext("Custom Theming Properties", () => { - before(() => { - cy.login() - cy.createTestApp() - cy.navigateToFrontend() - }) +import filterTests from "../support/filterTests" - /* Default Values: - Button roundness = Large - Accent colour = Blue 600 - Accent colour (hover) = Blue 500 - Navigation bar background colour = Gray 100 - Navigation bar text colour = Gray 800 */ - it("should reset the color property values", () => { - // Open Theme modal and change colours - cy.get(".spectrum-ActionButton-label").contains("Theme").click() - cy.get(".spectrum-Picker").contains("Large").click() - .parents() - .get(".spectrum-Menu-itemLabel").contains("None").click() - changeThemeColors() - // Reset colours - cy.get(".spectrum-Button-label").contains("Reset").click({force: true}) - // Check values have reset - checkThemeColorDefaults() - }) - - /* Button Roundness Values: - None = 0 - Small = 4px - Medium = 8px - Large = 16px */ - it("should test button roundness", () => { - const buttonRoundnessValues = ["0", "4px", "8px", "16px"] - cy.wait(1000) - // Add button, change roundness and confirm value - cy.addComponent("Button", null).then((componentId) => { - buttonRoundnessValues.forEach(function (item, index){ - cy.get(".spectrum-ActionButton-label").contains("Theme").click() - cy.get(".setting").contains("Button roundness").parent() - .get(".select-wrapper").click() - cy.get(".spectrum-Popover").find('li').eq(index).click() - cy.get(".spectrum-Button").contains("View changes").click({force: true}) - cy.reload() - cy.getComponent(componentId) - .parents(".svelte-xiqd1c").eq(0).should('have.attr', 'style').and('contains', `--buttonBorderRadius:${item}`) +filterTests(['all'], () => { + xcontext("Custom Theming Properties", () => { + before(() => { + cy.login() + cy.createTestApp() + cy.navigateToFrontend() + }) + + /* Default Values: + Button roundness = Large + Accent colour = Blue 600 + Accent colour (hover) = Blue 500 + Navigation bar background colour = Gray 100 + Navigation bar text colour = Gray 800 */ + it("should reset the color property values", () => { + // Open Theme modal and change colours + cy.get(".spectrum-ActionButton-label").contains("Theme").click() + cy.get(".spectrum-Picker").contains("Large").click() + .parents() + .get(".spectrum-Menu-itemLabel").contains("None").click() + changeThemeColors() + // Reset colours + cy.get(".spectrum-Button-label").contains("Reset").click({force: true}) + // Check values have reset + checkThemeColorDefaults() + }) + + /* Button Roundness Values: + None = 0 + Small = 4px + Medium = 8px + Large = 16px */ + it("should test button roundness", () => { + const buttonRoundnessValues = ["0", "4px", "8px", "16px"] + cy.wait(1000) + // Add button, change roundness and confirm value + cy.addComponent("Button", null).then((componentId) => { + buttonRoundnessValues.forEach(function (item, index){ + cy.get(".spectrum-ActionButton-label").contains("Theme").click() + cy.get(".setting").contains("Button roundness").parent() + .get(".select-wrapper").click() + cy.get(".spectrum-Popover").find('li').eq(index).click() + cy.get(".spectrum-Button").contains("View changes").click({force: true}) + cy.reload() + cy.getComponent(componentId) + .parents(".svelte-xiqd1c").eq(0).should('have.attr', 'style').and('contains', `--buttonBorderRadius:${item}`) + }) }) }) + + const changeThemeColors = () => { + // Changes the theme colours + cy.get(".spectrum-FieldLabel").contains("Accent color") + .parent().find(".container.svelte-z3cm5a").click() + .find('[title="Red 400"]').click() + cy.get(".spectrum-FieldLabel").contains("Accent color (hover)") + .parent().find(".container.svelte-z3cm5a").click() + .find('[title="Orange 400"]').click() + cy.get(".spectrum-FieldLabel").contains("Navigation bar background color") + .parent().find(".container.svelte-z3cm5a").click() + .find('[title="Yellow 400"]').click() + cy.get(".spectrum-FieldLabel").contains("Navigation bar text color") + .parent().find(".container.svelte-z3cm5a").click() + .find('[title="Green 400"]').click() + } + + const checkThemeColorDefaults = () => { + cy.get(".spectrum-FieldLabel").contains("Accent color") + .parent().find(".container.svelte-z3cm5a").click() + .get('[title="Blue 600"]').children().find('[aria-label="Checkmark"]') + cy.get(".spectrum-Dialog-grid").click() + cy.get(".spectrum-FieldLabel").contains("Accent color (hover)") + .parent().find(".container.svelte-z3cm5a").click() + .get('[title="Blue 500"]').children().find('[aria-label="Checkmark"]') + cy.get(".spectrum-Dialog-grid").click() + cy.get(".spectrum-FieldLabel").contains("Navigation bar background color") + .parent().find(".container.svelte-z3cm5a").click() + .get('[title="Gray 100"]').children().find('[aria-label="Checkmark"]') + cy.get(".spectrum-Dialog-grid").click() + cy.get(".spectrum-FieldLabel").contains("Navigation bar text color") + .parent().find(".container.svelte-z3cm5a").click() + .get('[title="Gray 800"]').children().find('[aria-label="Checkmark"]') + } }) - - const changeThemeColors = () => { - // Changes the theme colours - cy.get(".spectrum-FieldLabel").contains("Accent color") - .parent().find(".container.svelte-z3cm5a").click() - .find('[title="Red 400"]').click() - cy.get(".spectrum-FieldLabel").contains("Accent color (hover)") - .parent().find(".container.svelte-z3cm5a").click() - .find('[title="Orange 400"]').click() - cy.get(".spectrum-FieldLabel").contains("Navigation bar background color") - .parent().find(".container.svelte-z3cm5a").click() - .find('[title="Yellow 400"]').click() - cy.get(".spectrum-FieldLabel").contains("Navigation bar text color") - .parent().find(".container.svelte-z3cm5a").click() - .find('[title="Green 400"]').click() - } - - const checkThemeColorDefaults = () => { - cy.get(".spectrum-FieldLabel").contains("Accent color") - .parent().find(".container.svelte-z3cm5a").click() - .get('[title="Blue 600"]').children().find('[aria-label="Checkmark"]') - cy.get(".spectrum-Dialog-grid").click() - cy.get(".spectrum-FieldLabel").contains("Accent color (hover)") - .parent().find(".container.svelte-z3cm5a").click() - .get('[title="Blue 500"]').children().find('[aria-label="Checkmark"]') - cy.get(".spectrum-Dialog-grid").click() - cy.get(".spectrum-FieldLabel").contains("Navigation bar background color") - .parent().find(".container.svelte-z3cm5a").click() - .get('[title="Gray 100"]').children().find('[aria-label="Checkmark"]') - cy.get(".spectrum-Dialog-grid").click() - cy.get(".spectrum-FieldLabel").contains("Navigation bar text color") - .parent().find(".container.svelte-z3cm5a").click() - .get('[title="Gray 800"]').children().find('[aria-label="Checkmark"]') - } - }) diff --git a/packages/builder/cypress/integration/datasources/datasourceWizard.spec.js b/packages/builder/cypress/integration/datasources/datasourceWizard.spec.js new file mode 100644 index 0000000000..1bee7b5ec1 --- /dev/null +++ b/packages/builder/cypress/integration/datasources/datasourceWizard.spec.js @@ -0,0 +1,43 @@ +import filterTests from "../../support/filterTests" + +filterTests(['all'], () => { + context("Datasource Wizard", () => { + if (Cypress.env("TEST_ENV")) { + before(() => { + cy.login() + cy.createTestApp() + }) + + it("should navigate in and out of a datasource via wizard", () => { + // Select PostgreSQL and add config (without fetch) + const datasource = "Oracle" + cy.selectExternalDatasource(datasource) + cy.addDatasourceConfig(datasource, true) + + // Navigate back within datasource wizard + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Button").contains("Back").click({ force: true }) + cy.wait(1000) + }) + + // Select PostgreSQL datasource again + cy.get(".item-list").contains(datasource).click() + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Button").contains("Continue").click({ force: true }) + }) + + // Fetch tables after selection + // Previously entered config should not have been saved + // Config is back to default values + // Modal will close and provide 500 error + cy.intercept('**/datasources').as('datasourceConnection') + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Button").contains("Save and fetch tables").click({ force: true }) + }) + cy.wait("@datasourceConnection") + cy.get("@datasourceConnection").its('response.body') + .should('have.property', 'status', 500) + }) + } + }) +}) diff --git a/packages/builder/cypress/integration/datasources/mySql.spec.js b/packages/builder/cypress/integration/datasources/mySql.spec.js new file mode 100644 index 0000000000..03f59a6004 --- /dev/null +++ b/packages/builder/cypress/integration/datasources/mySql.spec.js @@ -0,0 +1,222 @@ +import filterTests from "../../support/filterTests" + +filterTests(["all"], () => { + context("MySQL Datasource Testing", () => { + if (Cypress.env("TEST_ENV")) { + before(() => { + cy.login() + cy.createTestApp() + }) + const datasource = "MySQL" + const queryName = "Cypress Test Query" + const queryRename = "CT Query Rename" + + it("Should add MySQL data source without configuration", () => { + // Select MySQL data source + cy.selectExternalDatasource(datasource) + // Attempt to fetch tables without applying configuration + cy.intercept("**/datasources").as("datasource") + cy.get(".spectrum-Button") + .contains("Save and fetch tables") + .click({ force: true }) + // Intercept Request after button click & apply assertions + cy.wait("@datasource") + cy.get("@datasource") + .its("response.body") + .should( + "have.property", + "message", + "connect ECONNREFUSED 127.0.0.1:3306" + ) + cy.get("@datasource") + .its("response.body") + .should("have.property", "status", 500) + }) + + it("should add MySQL data source and fetch tables", () => { + // Add & configure MySQL data source + cy.selectExternalDatasource(datasource) + cy.intercept("**/datasources").as("datasource") + cy.addDatasourceConfig(datasource) + // Check response from datasource after adding configuration + cy.wait("@datasource") + cy.get("@datasource").its("response.statusCode").should("eq", 200) + // Confirm fetch tables was successful + cy.get(".spectrum-Table") + .eq(0) + .find(".spectrum-Table-row") + .its("length") + .should("be.gt", 0) + }) + + it("should check table fetching error", () => { + // MySQL test data source contains tables without primary keys + cy.get(".spectrum-InLineAlert") + .should("contain", "Error fetching tables") + .and("contain", "No primary key constraint found") + }) + + it("should define a One relationship type", () => { + // Select relationship type & configure + cy.get(".spectrum-Button") + .contains("Define relationship") + .click({ force: true }) + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Picker").eq(0).click() + cy.get(".spectrum-Popover").contains("One").click() + cy.get(".spectrum-Picker").eq(1).click() + cy.get(".spectrum-Popover").contains("REGIONS").click() + cy.get(".spectrum-Picker").eq(2).click() + cy.get(".spectrum-Popover").contains("REGION_ID").click() + cy.get(".spectrum-Picker").eq(3).click() + cy.get(".spectrum-Popover").contains("COUNTRIES").click() + cy.get(".spectrum-Picker").eq(4).click() + cy.get(".spectrum-Popover").contains("REGION_ID").click() + // Save relationship & reload page + cy.get(".spectrum-Button").contains("Save").click({ force: true }) + cy.reload() + }) + // Confirm table length & column name + cy.get(".spectrum-Table") + .eq(1) + .find(".spectrum-Table-row") + .its("length") + .should("eq", 1) + cy.get(".spectrum-Table-cell").should("contain", "COUNTRIES to REGIONS") + }) + + it("should define a Many relationship type", () => { + // Select relationship type & configure + cy.get(".spectrum-Button") + .contains("Define relationship") + .click({ force: true }) + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Picker").eq(0).click() + cy.get(".spectrum-Popover").contains("Many").click() + cy.get(".spectrum-Picker").eq(1).click() + cy.get(".spectrum-Popover").contains("LOCATIONS").click() + cy.get(".spectrum-Picker").eq(2).click() + cy.get(".spectrum-Popover").contains("REGIONS").click() + cy.get(".spectrum-Picker").eq(3).click() + cy.get(".spectrum-Popover").contains("COUNTRIES").click() + cy.get(".spectrum-Picker").eq(4).click() + cy.get(".spectrum-Popover").contains("COUNTRY_ID").click() + cy.get(".spectrum-Picker").eq(5).click() + cy.get(".spectrum-Popover").contains("REGION_ID").click() + // Save relationship & reload page + cy.get(".spectrum-Button").contains("Save").click({ force: true }) + cy.reload() + cy.wait(1000) + }) + // Confirm table length & relationship name + cy.get(".spectrum-Table") + .eq(1) + .find(".spectrum-Table-row") + .its("length") + .should("eq", 2) + cy.get(".spectrum-Table-cell").should( + "contain", + "LOCATIONS through COUNTRIES → REGIONS" + ) + }) + + it("should delete relationships", () => { + // Delete both relationships + cy.get(".spectrum-Table") + .eq(1) + .find(".spectrum-Table-row") + .its("length") + .then(len => { + for (let i = 0; i < len; i++) { + cy.get(".spectrum-Table") + .eq(1) + .within(() => { + cy.get(".spectrum-Table-row").eq(0).click() + cy.wait(500) + }) + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Button") + .contains("Delete") + .click({ force: true }) + }) + cy.reload() + } + // Confirm relationships no longer exist + cy.get(".spectrum-Body").should( + "contain", + "No relationships configured" + ) + }) + }) + + it("should add a query", () => { + // Add query + cy.get(".spectrum-Button").contains("Add query").click({ force: true }) + cy.get(".spectrum-Form-item") + .eq(0) + .within(() => { + cy.get("input").type(queryName) + }) + // Insert Query within Fields section + cy.get(".CodeMirror textarea") + .eq(0) + .type("SELECT * FROM books", { force: true }) + // Intercept query execution + cy.intercept("**/queries/preview").as("query") + cy.get(".spectrum-Button").contains("Run Query").click({ force: true }) + cy.wait(500) + cy.wait("@query") + // Assert against Status Code & Body + cy.get("@query").its("response.statusCode").should("eq", 200) + cy.get("@query").its("response.body").should("not.be.empty") + // Save query + cy.get(".spectrum-Button").contains("Save Query").click({ force: true }) + cy.get(".nav-item").should("contain", queryName) + }) + + it("should duplicate a query", () => { + // Get last nav item - The query + cy.get(".nav-item") + .last() + .within(() => { + cy.get(".icon").eq(1).click({ force: true }) + }) + // Select and confirm duplication + cy.get(".spectrum-Menu").contains("Duplicate").click() + cy.get(".nav-item").should("contain", queryName + " (1)") + }) + + it("should edit a query name", () => { + // Rename query + cy.get(".spectrum-Form-item") + .eq(0) + .within(() => { + cy.get("input").clear().type(queryRename) + }) + // Save query + cy.get(".spectrum-Button").contains("Save Query").click({ force: true }) + cy.get(".nav-item").should("contain", queryRename) + }) + + it("should delete a query", () => { + // Get last nav item - The query + for (let i = 0; i < 2; i++) { + cy.get(".nav-item") + .last() + .within(() => { + cy.get(".icon").eq(1).click({ force: true }) + }) + // Select Delete + cy.get(".spectrum-Menu").contains("Delete").click() + cy.get(".spectrum-Button") + .contains("Delete Query") + .click({ force: true }) + cy.wait(1000) + } + // Confirm deletion + cy.get(".nav-item").should("not.contain", queryName) + cy.get(".nav-item").should("not.contain", queryRename) + }) + } + }) +}) diff --git a/packages/builder/cypress/integration/datasources/oracle.spec.js b/packages/builder/cypress/integration/datasources/oracle.spec.js new file mode 100644 index 0000000000..73c25001c9 --- /dev/null +++ b/packages/builder/cypress/integration/datasources/oracle.spec.js @@ -0,0 +1,230 @@ +import filterTests from "../../support/filterTests" + +filterTests(["all"], () => { + context("Oracle Datasource Testing", () => { + if (Cypress.env("TEST_ENV")) { + before(() => { + cy.login() + cy.createTestApp() + }) + const datasource = "Oracle" + const queryName = "Cypress Test Query" + const queryRename = "CT Query Rename" + + it("Should add Oracle data source and skip table fetch", () => { + // Select Oracle data source + cy.selectExternalDatasource(datasource) + // Skip table fetch - no config added + cy.get(".spectrum-Button") + .contains("Skip table fetch") + .click({ force: true }) + cy.wait(500) + // Confirm config contains localhost + cy.get(".spectrum-Textfield-input") + .eq(1) + .should("have.value", "localhost") + // Add another Oracle data source, configure & skip table fetch + cy.selectExternalDatasource(datasource) + cy.addDatasourceConfig(datasource, true) + // Confirm config and no tables + cy.get(".spectrum-Textfield-input") + .eq(1) + .should("have.value", Cypress.env("oracle").HOST) + cy.get(".spectrum-Body").eq(2).should("contain", "No tables found.") + }) + + it("Should add Oracle data source and fetch tables without configuration", () => { + // Select Oracle data source + cy.selectExternalDatasource(datasource) + // Attempt to fetch tables without applying configuration + cy.intercept("**/datasources").as("datasource") + cy.get(".spectrum-Button") + .contains("Save and fetch tables") + .click({ force: true }) + // Intercept Request after button click & apply assertions + cy.wait("@datasource") + cy.get("@datasource") + .its("response.body") + .should("have.property", "status", 500) + }) + + it("should add Oracle data source and fetch tables", () => { + // Add & configure Oracle data source + cy.selectExternalDatasource(datasource) + cy.intercept("**/datasources").as("datasource") + cy.addDatasourceConfig(datasource) + // Check response from datasource after adding configuration + cy.wait("@datasource") + cy.get("@datasource").its("response.statusCode").should("eq", 200) + // Confirm fetch tables was successful + cy.get(".spectrum-Table") + .eq(0) + .find(".spectrum-Table-row") + .its("length") + .should("be.gt", 0) + }) + + it("should define a One relationship type", () => { + // Select relationship type & configure + cy.get(".spectrum-Button") + .contains("Define relationship") + .click({ force: true }) + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Picker").eq(0).click() + cy.get(".spectrum-Popover").contains("One").click() + cy.get(".spectrum-Picker").eq(1).click() + cy.get(".spectrum-Popover").contains("REGIONS").click() + cy.get(".spectrum-Picker").eq(2).click() + cy.get(".spectrum-Popover").contains("REGION_ID").click() + cy.get(".spectrum-Picker").eq(3).click() + cy.get(".spectrum-Popover").contains("COUNTRIES").click() + cy.get(".spectrum-Picker").eq(4).click() + cy.get(".spectrum-Popover").contains("REGION_ID").click() + // Save relationship & reload page + cy.get(".spectrum-Button").contains("Save").click({ force: true }) + cy.reload() + }) + // Confirm table length & column name + cy.get(".spectrum-Table") + .eq(1) + .find(".spectrum-Table-row") + .its("length") + .should("eq", 1) + cy.get(".spectrum-Table-cell").should("contain", "COUNTRIES to REGIONS") + }) + + it("should define a Many relationship type", () => { + // Select relationship type & configure + cy.get(".spectrum-Button") + .contains("Define relationship") + .click({ force: true }) + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Picker").eq(0).click() + cy.get(".spectrum-Popover").contains("Many").click() + cy.get(".spectrum-Picker").eq(1).click() + cy.get(".spectrum-Popover").contains("LOCATIONS").click() + cy.get(".spectrum-Picker").eq(2).click() + cy.get(".spectrum-Popover").contains("REGIONS").click() + cy.get(".spectrum-Picker").eq(3).click() + cy.get(".spectrum-Popover").contains("COUNTRIES").click() + cy.get(".spectrum-Picker").eq(4).click() + cy.get(".spectrum-Popover").contains("COUNTRY_ID").click() + cy.get(".spectrum-Picker").eq(5).click() + cy.get(".spectrum-Popover").contains("REGION_ID").click() + // Save relationship & reload page + cy.get(".spectrum-Button").contains("Save").click({ force: true }) + cy.reload() + }) + // Confirm table length & relationship name + cy.get(".spectrum-Table") + .eq(1) + .find(".spectrum-Table-row") + .its("length") + .should("eq", 2) + cy.get(".spectrum-Table-cell").should( + "contain", + "LOCATIONS through COUNTRIES → REGIONS" + ) + }) + + it("should delete relationships", () => { + // Delete both relationships + cy.get(".spectrum-Table") + .eq(1) + .find(".spectrum-Table-row") + .its("length") + .then(len => { + for (let i = 0; i < len; i++) { + cy.get(".spectrum-Table") + .eq(1) + .within(() => { + cy.get(".spectrum-Table-row").eq(0).click() + cy.wait(500) + }) + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Button") + .contains("Delete") + .click({ force: true }) + }) + cy.reload() + } + // Confirm relationships no longer exist + cy.get(".spectrum-Body").should( + "contain", + "No relationships configured" + ) + }) + }) + + it("should add a query", () => { + // Add query + cy.get(".spectrum-Button").contains("Add query").click({ force: true }) + cy.get(".spectrum-Form-item") + .eq(0) + .within(() => { + cy.get("input").type(queryName) + }) + // Insert Query within Fields section + cy.get(".CodeMirror textarea") + .eq(0) + .type("SELECT * FROM JOBS", { force: true }) + // Intercept query execution + cy.intercept("**/queries/preview").as("query") + cy.get(".spectrum-Button").contains("Run Query").click({ force: true }) + cy.wait(500) + cy.wait("@query") + // Assert against Status Code & Body + cy.get("@query").its("response.statusCode").should("eq", 200) + cy.get("@query").its("response.body").should("not.be.empty") + // Save query + cy.get(".spectrum-Button").contains("Save Query").click({ force: true }) + cy.get(".nav-item").should("contain", queryName) + }) + + it("should duplicate a query", () => { + // Get query nav item + cy.get(".nav-item") + .contains(queryName) + .parent() + .within(() => { + cy.get(".spectrum-Icon").eq(1).click({ force: true }) + }) + // Select and confirm duplication + cy.get(".spectrum-Menu").contains("Duplicate").click() + cy.get(".nav-item").should("contain", queryName + " (1)") + }) + + it("should edit a query name", () => { + // Rename query + cy.get(".spectrum-Form-item") + .eq(0) + .within(() => { + cy.get("input").clear().type(queryRename) + }) + // Save query + cy.get(".spectrum-Button").contains("Save Query").click({ force: true }) + cy.get(".nav-item").should("contain", queryRename) + }) + + it("should delete a query", () => { + // Get query nav item - QueryName + cy.get(".nav-item") + .contains(queryName) + .parent() + .within(() => { + cy.get(".spectrum-Icon").eq(1).click({ force: true }) + }) + + // Select Delete + cy.get(".spectrum-Menu").contains("Delete").click() + cy.get(".spectrum-Button") + .contains("Delete Query") + .click({ force: true }) + cy.wait(1000) + + // Confirm deletion + cy.get(".nav-item").should("not.contain", queryName) + }) + } + }) +}) diff --git a/packages/builder/cypress/integration/datasources/postgreSql.spec.js b/packages/builder/cypress/integration/datasources/postgreSql.spec.js new file mode 100644 index 0000000000..3f55636623 --- /dev/null +++ b/packages/builder/cypress/integration/datasources/postgreSql.spec.js @@ -0,0 +1,285 @@ +import filterTests from "../../support/filterTests" + +filterTests(["all"], () => { + context("PostgreSQL Datasource Testing", () => { + if (Cypress.env("TEST_ENV")) { + before(() => { + cy.login() + cy.createTestApp() + }) + const datasource = "PostgreSQL" + const queryName = "Cypress Test Query" + const queryRename = "CT Query Rename" + + it("Should add PostgreSQL data source without configuration", () => { + // Select PostgreSQL data source + cy.selectExternalDatasource(datasource) + // Attempt to fetch tables without applying configuration + cy.intercept("**/datasources").as("datasource") + cy.get(".spectrum-Button") + .contains("Save and fetch tables") + .click({ force: true }) + // Intercept Request after button click & apply assertions + cy.wait("@datasource") + cy.get("@datasource") + .its("response.body") + .should( + "have.property", + "message", + "connect ECONNREFUSED 127.0.0.1:5432" + ) + cy.get("@datasource") + .its("response.body") + .should("have.property", "status", 500) + }) + + it("should add PostgreSQL data source and fetch tables", () => { + // Add & configure PostgreSQL data source + cy.selectExternalDatasource(datasource) + cy.intercept("**/datasources").as("datasource") + cy.addDatasourceConfig(datasource) + // Check response from datasource after adding configuration + cy.wait("@datasource") + cy.get("@datasource").its("response.statusCode").should("eq", 200) + // Confirm fetch tables was successful + cy.get(".spectrum-Table") + .eq(0) + .find(".spectrum-Table-row") + .its("length") + .should("be.gt", 0) + }) + + it("should define a One relationship type", () => { + // Select relationship type & configure + cy.get(".spectrum-Button") + .contains("Define relationship") + .click({ force: true }) + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Picker").eq(0).click() + cy.get(".spectrum-Popover").contains("One").click() + cy.get(".spectrum-Picker").eq(1).click() + cy.get(".spectrum-Popover").contains("REGIONS").click() + cy.get(".spectrum-Picker").eq(2).click() + cy.get(".spectrum-Popover").contains("REGION_ID").click() + cy.get(".spectrum-Picker").eq(3).click() + cy.get(".spectrum-Popover").contains("COUNTRIES").click() + cy.get(".spectrum-Picker").eq(4).click() + cy.get(".spectrum-Popover").contains("REGION_ID").click() + // Save relationship & reload page + cy.get(".spectrum-Button").contains("Save").click({ force: true }) + cy.reload() + }) + // Confirm table length & column name + cy.get(".spectrum-Table") + .eq(1) + .find(".spectrum-Table-row") + .its("length") + .should("eq", 1) + cy.get(".spectrum-Table-cell").should("contain", "COUNTRIES to REGIONS") + }) + + it("should define a Many relationship type", () => { + // Select relationship type & configure + cy.get(".spectrum-Button") + .contains("Define relationship") + .click({ force: true }) + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Picker").eq(0).click() + cy.get(".spectrum-Popover").contains("Many").click() + cy.get(".spectrum-Picker").eq(1).click() + cy.get(".spectrum-Popover").contains("LOCATIONS").click() + cy.get(".spectrum-Picker").eq(2).click() + cy.get(".spectrum-Popover").contains("REGIONS").click() + cy.get(".spectrum-Picker").eq(3).click() + cy.get(".spectrum-Popover").contains("COUNTRIES").click() + cy.get(".spectrum-Picker").eq(4).click() + cy.get(".spectrum-Popover").contains("COUNTRY_ID").click() + cy.get(".spectrum-Picker").eq(5).click() + cy.get(".spectrum-Popover").contains("REGION_ID").click() + // Save relationship & reload page + cy.get(".spectrum-Button").contains("Save").click({ force: true }) + cy.reload() + }) + // Confirm table length & relationship name + cy.get(".spectrum-Table") + .eq(1) + .find(".spectrum-Table-row") + .its("length") + .should("eq", 2) + cy.get(".spectrum-Table-cell").should( + "contain", + "LOCATIONS through COUNTRIES → REGIONS" + ) + }) + + it("should delete a relationship", () => { + cy.get(".hierarchy-items-container").contains(datasource).click() + cy.reload() + // Delete one relationship + cy.get(".spectrum-Table") + .eq(1) + .within(() => { + cy.get(".spectrum-Table-row").eq(0).click() + cy.wait(500) + }) + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Button").contains("Delete").click({ force: true }) + }) + cy.reload() + // Confirm relationship was deleted + cy.get(".spectrum-Table") + .eq(1) + .find(".spectrum-Table-row") + .its("length") + .should("eq", 1) + }) + + it("should add a query", () => { + // Add query + cy.get(".spectrum-Button").contains("Add query").click({ force: true }) + cy.get(".spectrum-Form-item") + .eq(0) + .within(() => { + cy.get("input").type(queryName) + }) + // Insert Query within Fields section + cy.get(".CodeMirror textarea") + .eq(0) + .type("SELECT * FROM books", { force: true }) + // Intercept query execution + cy.intercept("**/queries/preview").as("query") + cy.get(".spectrum-Button").contains("Run Query").click({ force: true }) + cy.wait(500) + cy.wait("@query") + // Assert against Status Code & Body + cy.get("@query").its("response.statusCode").should("eq", 200) + cy.get("@query").its("response.body").should("not.be.empty") + // Save query + cy.get(".spectrum-Button").contains("Save Query").click({ force: true }) + cy.get(".hierarchy-items-container").should("contain", queryName) + }) + + it("should switch to schema with no tables", () => { + // Switch Schema - To one without any tables + cy.get(".hierarchy-items-container").contains(datasource).click() + switchSchema("randomText") + + // No tables displayed + cy.get(".spectrum-Body").eq(2).should("contain", "No tables found") + + // Previously created query should be visible + cy.get(".spectrum-Table").should("contain", queryName) + }) + + it("should switch schemas", () => { + // Switch schema - To one with tables + switchSchema("1") + + // Confirm tables exist - Check for specific one + cy.get(".spectrum-Table").eq(0).should("contain", "test") + cy.get(".spectrum-Table") + .eq(0) + .find(".spectrum-Table-row") + .its("length") + .should("eq", 1) + + // Confirm specific table visible within left nav bar + cy.get(".hierarchy-items-container").should("contain", "test") + + // Switch back to public schema + switchSchema("public") + + // Confirm tables exist - again + cy.get(".spectrum-Table").eq(0).should("contain", "REGIONS") + cy.get(".spectrum-Table") + .eq(0) + .find(".spectrum-Table-row") + .its("length") + .should("be.gt", 1) + + // Confirm specific table visible within left nav bar + cy.get(".hierarchy-items-container").should("contain", "REGIONS") + + // No relationships and one query + cy.get(".spectrum-Body") + .eq(3) + .should("contain", "No relationships configured.") + cy.get(".spectrum-Table").eq(1).should("contain", queryName) + }) + + it("should duplicate a query", () => { + // Get last nav item - The query + cy.get(".nav-item") + .last() + .within(() => { + cy.get(".icon").eq(1).click({ force: true }) + }) + // Select and confirm duplication + cy.get(".spectrum-Menu").contains("Duplicate").click() + cy.get(".nav-item").should("contain", queryName + " (1)") + }) + + it("should edit a query name", () => { + // Access query + cy.get(".hierarchy-items-container") + .contains(queryName + " (1)") + .click() + + // Rename query + cy.get(".spectrum-Form-item") + .eq(0) + .within(() => { + cy.get("input").clear().type(queryRename) + }) + + // Run and Save query + cy.get(".spectrum-Button").contains("Run Query").click({ force: true }) + cy.wait(500) + cy.get(".spectrum-Button").contains("Save Query").click({ force: true }) + cy.get(".nav-item").should("contain", queryRename) + }) + + it("should delete a query", () => { + // Get last nav item - The query + for (let i = 0; i < 2; i++) { + cy.get(".nav-item") + .last() + .within(() => { + cy.get(".icon").eq(1).click({ force: true }) + }) + // Select Delete + cy.get(".spectrum-Menu").contains("Delete").click() + cy.get(".spectrum-Button") + .contains("Delete Query") + .click({ force: true }) + cy.wait(1000) + } + // Confirm deletion + cy.get(".nav-item").should("not.contain", queryName) + cy.get(".nav-item").should("not.contain", queryRename) + }) + + const switchSchema = schema => { + // Edit configuration - Change Schema + cy.get(".spectrum-Textfield") + .eq(6) + .within(() => { + cy.get("input").clear().type(schema) + }) + // Save configuration & fetch + cy.get(".spectrum-Button").contains("Save").click({ force: true }) + cy.get(".spectrum-Button") + .contains("Fetch tables") + .click({ force: true }) + // Click fetch tables again within modal + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Button") + .contains("Fetch tables") + .click({ force: true }) + }) + cy.reload() + cy.wait(5000) + } + } + }) +}) diff --git a/packages/builder/cypress/integration/datasources/rest.spec.js b/packages/builder/cypress/integration/datasources/rest.spec.js new file mode 100644 index 0000000000..a15487c01b --- /dev/null +++ b/packages/builder/cypress/integration/datasources/rest.spec.js @@ -0,0 +1,47 @@ +import filterTests from "../../support/filterTests" + +filterTests(["smoke", "all"], () => { + context("REST Datasource Testing", () => { + before(() => { + cy.login() + cy.createTestApp() + }) + + const datasource = "REST" + const restUrl = "https://api.openbrewerydb.org/breweries" + + it("Should add REST data source with incorrect API", () => { + // Select REST data source + cy.selectExternalDatasource(datasource) + // Enter incorrect api & attempt to send query + cy.wait(500) + cy.get(".spectrum-Button").contains("Add query").click({ force: true }) + cy.intercept("**/preview").as("queryError") + cy.get("input").clear().type("random text") + cy.get(".spectrum-Button").contains("Send").click({ force: true }) + // Intercept Request after button click & apply assertions + cy.wait("@queryError") + cy.get("@queryError") + .its("response.body") + .should("have.property", "message", "Invalid URL: http://random text?") + cy.get("@queryError") + .its("response.body") + .should("have.property", "status", 400) + }) + + it("should add and configure a REST datasource", () => { + // Select REST datasource and create query + cy.selectExternalDatasource(datasource) + cy.wait(500) + // createRestQuery confirms query creation + cy.createRestQuery("GET", restUrl, "/breweries") + // Confirm status code response within REST datasource + cy.wait(1000) + cy.get(".stats").within(() => { + cy.get(".spectrum-FieldLabel") + .eq(0) + .should("contain", 200) + }) + }) + }) +}) diff --git a/packages/builder/cypress/integration/queryLevelTransformers.spec.js b/packages/builder/cypress/integration/queryLevelTransformers.spec.js new file mode 100644 index 0000000000..e96a6dba29 --- /dev/null +++ b/packages/builder/cypress/integration/queryLevelTransformers.spec.js @@ -0,0 +1,140 @@ +import filterTests from "../support/filterTests" + +filterTests(["smoke", "all"], () => { + context("Query Level Transformers", () => { + before(() => { + cy.login() + cy.deleteApp("Cypress Tests") + cy.createApp("Cypress Tests") + }) + + it("should write a transformer function", () => { + // Add REST datasource - contains API for breweries + const datasource = "REST" + const restUrl = "https://api.openbrewerydb.org/breweries" + cy.selectExternalDatasource(datasource) + cy.createRestQuery("GET", restUrl, "/breweries") + cy.get(".spectrum-Tabs-itemLabel").contains("Transformer").click() + // Get Transformer Function from file + cy.readFile("cypress/support/queryLevelTransformerFunction.js").then( + transformerFunction => { + cy.get(".CodeMirror textarea") + // Highlight current text and overwrite with file contents + .type(Cypress.platform === "darwin" ? "{cmd}a" : "{ctrl}a", { + force: true, + }) + .type(transformerFunction, { parseSpecialCharSequences: false }) + } + ) + // Send Query + cy.intercept("**/queries/preview").as("query") + cy.get(".spectrum-Button").contains("Send").click({ force: true }) + cy.wait("@query") + // Assert against Status Code, body, & body rows + cy.get("@query").its("response.statusCode").should("eq", 200) + cy.get("@query").its("response.body").should("not.be.empty") + cy.get("@query").its("response.body.rows").should("not.be.empty") + }) + + it("should add data to the previous query", () => { + // Add REST datasource - contains API for breweries + const datasource = "REST" + const restUrl = "https://api.openbrewerydb.org/breweries" + cy.selectExternalDatasource(datasource) + cy.createRestQuery("GET", restUrl, "/breweries") + cy.get(".spectrum-Tabs-itemLabel").contains("Transformer").click() + // Get Transformer Function with Data from file + cy.readFile( + "cypress/support/queryLevelTransformerFunctionWithData.js" + ).then(transformerFunction => { + //console.log(transformerFunction[1]) + cy.get(".CodeMirror textarea") + // Highlight current text and overwrite with file contents + .type(Cypress.platform === "darwin" ? "{cmd}a" : "{ctrl}a", { + force: true, + }) + .type(transformerFunction, { parseSpecialCharSequences: false }) + }) + // Send Query + cy.intercept("**/queries/preview").as("query") + cy.get(".spectrum-Button").contains("Send").click({ force: true }) + cy.wait("@query") + // Assert against Status Code, body, & body rows + cy.get("@query").its("response.statusCode").should("eq", 200) + cy.get("@query").its("response.body").should("not.be.empty") + cy.get("@query").its("response.body.rows").should("not.be.empty") + }) + + it("should run an invalid query within the transformer section", () => { + // Add REST datasource - contains API for breweries + const datasource = "REST" + const restUrl = "https://api.openbrewerydb.org/breweries" + cy.selectExternalDatasource(datasource) + cy.createRestQuery("GET", restUrl, "/breweries") + cy.get(".spectrum-Tabs-itemLabel").contains("Transformer").click() + // Clear the code box and add "test" + cy.get(".CodeMirror textarea") + .type(Cypress.platform === "darwin" ? "{cmd}a" : "{ctrl}a", { + force: true, + }) + .type("test") + // Run Query and intercept + cy.intercept("**/preview").as("queryError") + cy.get(".spectrum-Button").contains("Send").click({ force: true }) + cy.wait("@queryError") + cy.wait(500) + // Assert against message and status for the query error + cy.get("@queryError") + .its("response.body") + .should("have.property", "message", "test is not defined") + cy.get("@queryError") + .its("response.body") + .should("have.property", "status", 400) + }) + + xit("should run an invalid query via POST request", () => { + // POST request with transformer as null + cy.request({ + method: "POST", + url: `${Cypress.config().baseUrl}/api/queries/`, + body: { + fields: { headers: {}, queryString: null, path: null }, + parameters: [], + schema: {}, + name: "test", + queryVerb: "read", + transformer: null, + datasourceId: "test", + }, + // Expected 400 error - Transformer must be a string + failOnStatusCode: false, + }).then(response => { + expect(response.status).to.equal(400) + expect(response.body.message).to.include( + 'Invalid body - "transformer" must be a string' + ) + }) + }) + + xit("should run an empty query", () => { + // POST request with Transformer as an empty string + cy.request({ + method: "POST", + url: `${Cypress.config().baseUrl}/api/queries/preview`, + body: { + fields: { headers: {}, queryString: null, path: null }, + queryVerb: "read", + transformer: "", + datasourceId: "test", + }, + // Expected 400 error - Transformer is not allowed to be empty + failOnStatusCode: false, + }).then(response => { + expect(response.status).to.equal(400) + expect(response.body.message).to.include( + 'Invalid body - "transformer" is not allowed to be empty' + ) + }) + }) + }) +}) diff --git a/packages/builder/cypress/integration/renameAnApplication.spec.js b/packages/builder/cypress/integration/renameAnApplication.spec.js index a954faee95..48e829fa03 100644 --- a/packages/builder/cypress/integration/renameAnApplication.spec.js +++ b/packages/builder/cypress/integration/renameAnApplication.spec.js @@ -1,103 +1,133 @@ -context("Rename an App", () => { - beforeEach(() => { - cy.login() - cy.createTestApp() - }) +import filterTests from "../support/filterTests" -it("should rename an unpublished application", () => { - const appRename = "Cypress Renamed" - // Rename app, Search for app, Confirm name was changed - cy.get(".home-logo").click() - renameApp(appRename) - cy.searchForApplication(appRename) - cy.get(".appTable").find(".title").should("have.length", 1) - cy.deleteApp(appRename) -}) - -xit("Should rename a published application", () => { - // It is not possible to rename a published application - const appRename = "Cypress Renamed" - // Publish the app - cy.get(".toprightnav") - cy.get(".spectrum-Button").contains("Publish").click({force: true}) - cy.get(".spectrum-Dialog-grid") - .within(() => { - // Click publish again within the modal - cy.get(".spectrum-Button").contains("Publish").click({force: true}) +filterTests(['all'], () => { + context("Rename an App", () => { + beforeEach(() => { + cy.login() + cy.createTestApp() }) - // Rename app, Search for app, Confirm name was changed - cy.get(".home-logo").click() - renameApp(appRename, true) - cy.searchForApplication(appRename) - cy.get(".appTable").find(".title").should("have.length", 1) -}) -it("Should try to rename an application to have no name", () => { - cy.get(".home-logo").click() - renameApp(" ", false, true) - // Close modal and confirm name has not been changed - cy.get(".spectrum-Dialog-grid").contains("Cancel").click() - cy.searchForApplication("Cypress Tests") - cy.get(".appTable").find(".title").should("have.length", 1) -}) - -xit("Should create two applications with the same name", () => { - // It is not possible to have applications with the same name - const appName = "Cypress Tests" - cy.visit(`localhost:${Cypress.env("PORT")}/builder`) - cy.wait(500) - cy.get(".spectrum-Button").contains("Create app").click({force: true}) - cy.contains(/Start from scratch/).click() - cy.get(".spectrum-Modal") - .within(() => { - cy.get("input").eq(0).type(appName) - cy.get(".spectrum-ButtonGroup").contains("Create app").click({force: true}) - cy.get(".error").should("have.text", "Another app with the same name already exists") + it("should rename an unpublished application", () => { + const appName = "Cypress Tests" + const appRename = "Cypress Renamed" + // Rename app, Search for app, Confirm name was changed + cy.get(".home-logo").click() + renameApp(appName, appRename) + cy.reload() + cy.wait(1000) + cy.searchForApplication(appRename) + cy.get(".appTable").find(".title").should("have.length", 1) + // Set app name back to Cypress Tests + cy.reload() + cy.wait(1000) + renameApp(appRename, appName) }) -}) - -it("should validate application names", () => { - // App name must be letters, numbers and spaces only - // This test checks numbers and special characters specifically - const numberName = 12345 - const specialCharName = "£$%^" - cy.get(".home-logo").click() - renameApp(numberName) - cy.searchForApplication(numberName) - cy.get(".appTable").find(".title").should("have.length", 1) - renameApp(specialCharName) - cy.get(".error").should("have.text", "App name must be letters, numbers and spaces only") -}) - - const renameApp = (appName, published, noName) => { - cy.request(`localhost:${Cypress.env("PORT")}/api/applications?status=all`) - .its("body") - .then(val => { - if (val.length > 0) { - cy.get(".appTable > :nth-child(5) > :nth-child(2) > .spectrum-Icon").click() - // Check for when an app is published - if (published == true){ - // Should not have Edit as option, will unpublish app - cy.should("not.have.value", "Edit") - cy.get(".spectrum-Menu").contains("Unpublish").click() - cy.get(".spectrum-Dialog-grid").contains("Unpublish app").click() - cy.get(".appTable > :nth-child(5) > :nth-child(2) > .spectrum-Icon").click() - } - cy.contains("Edit").click() - cy.get(".spectrum-Modal") + + xit("Should rename a published application", () => { + // It is not possible to rename a published application + const appName = "Cypress Tests" + const appRename = "Cypress Renamed" + // Publish the app + cy.get(".toprightnav") + cy.get(".spectrum-Button").contains("Publish").click({force: true}) + cy.get(".spectrum-Dialog-grid") .within(() => { - if (noName == true){ - cy.get("input").clear() - cy.get(".spectrum-Dialog-grid").click() - .contains("App name must be letters, numbers and spaces only") - return cy + // Click publish again within the modal + cy.get(".spectrum-Button").contains("Publish").click({force: true}) + }) + // Rename app, Search for app, Confirm name was changed + cy.get(".home-logo").click() + renameApp(appName, appRename, true) + cy.searchForApplication(appRename) + cy.get(".appTable").find(".wrapper").should("have.length", 1) + }) + + it("Should try to rename an application to have no name", () => { + const appName = "Cypress Tests" + cy.get(".home-logo").click() + renameApp(appName, " ", false, true) + cy.wait(500) + // Close modal and confirm name has not been changed + cy.get(".spectrum-Dialog-grid").contains("Cancel").click() + cy.reload() + cy.wait(1000) + cy.searchForApplication(appName) + cy.get(".appTable").find(".title").should("have.length", 1) + + }) + + xit("Should create two applications with the same name", () => { + // It is not possible to have applications with the same name + const appName = "Cypress Tests" + cy.visit(`${Cypress.config().baseUrl}/builder`) + cy.wait(500) + cy.get(".spectrum-Button").contains("Create app").click({force: true}) + cy.contains(/Start from scratch/).click() + cy.get(".spectrum-Modal") + .within(() => { + cy.get("input").eq(0).type(appName) + cy.get(".spectrum-ButtonGroup").contains("Create app").click({force: true}) + cy.get(".error").should("have.text", "Another app with the same name already exists") + }) + }) + + it("should validate application names", () => { + // App name must be letters, numbers and spaces only + // This test checks numbers and special characters specifically + const appName = "Cypress Tests" + const numberName = 12345 + const specialCharName = "£$%^" + cy.get(".home-logo").click() + renameApp(appName, numberName) + cy.reload() + cy.wait(1000) + cy.searchForApplication(numberName) + cy.get(".appTable").find(".title").should("have.length", 1) + cy.reload() + cy.wait(1000) + renameApp(numberName, specialCharName) + cy.get(".error").should("have.text", "App name must be letters, numbers and spaces only") + // Set app name back to Cypress Tests + cy.reload() + cy.wait(1000) + renameApp(numberName, appName) + }) + + const renameApp = (originalName, changedName, published, noName) => { + cy.searchForApplication(originalName) + cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`) + .its("body") + .then(val => { + if (val.length > 0) { + cy.get(".appTable") + .within(() => { + cy.get(".spectrum-Icon").eq(1).click() + }) + // Check for when an app is published + if (published == true){ + // Should not have Edit as option, will unpublish app + cy.should("not.have.value", "Edit") + cy.get(".spectrum-Menu").contains("Unpublish").click() + cy.get(".spectrum-Dialog-grid").contains("Unpublish app").click() + cy.get(".appTable > :nth-child(5) > :nth-child(2) > .spectrum-Icon").click() } - cy.get("input").clear() - cy.get("input").eq(0).type(appName).should("have.value", appName).blur() - cy.get(".spectrum-ButtonGroup").contains("Save").click({force: true}) - cy.wait(500) - }) + cy.contains("Edit").click() + cy.get(".spectrum-Modal") + .within(() => { + if (noName == true){ + cy.get("input").clear() + cy.get(".spectrum-Dialog-grid").click() + .contains("App name must be letters, numbers and spaces only") + return cy + } + cy.get("input").clear() + cy.get("input").eq(0).type(changedName).should("have.value", changedName).blur() + cy.get(".spectrum-ButtonGroup").contains("Save").click({force: true}) + cy.wait(500) + }) + } + }) + } }) -} }) diff --git a/packages/builder/cypress/integration/revertApp.spec.js b/packages/builder/cypress/integration/revertApp.spec.js new file mode 100644 index 0000000000..c64d19f230 --- /dev/null +++ b/packages/builder/cypress/integration/revertApp.spec.js @@ -0,0 +1,67 @@ +import filterTests from "../support/filterTests" + +filterTests(['smoke', 'all'], () => { + context("Revert apps", () => { + before(() => { + cy.login() + cy.createTestApp() + }) + + it("should try to revert an unpublished app", () => { + // Click revert icon + cy.get(".toprightnav").within(() => { + cy.get(".spectrum-Icon").eq(1).click() + }) + cy.get(".spectrum-Dialog-grid").within(() => { + // Enter app name before revert + cy.get("input").type("Cypress Tests") + cy.intercept('**/revert').as('revertApp') + // Click Revert + cy.get(".spectrum-Button").contains("Revert").click({ force: true }) + // Intercept Request after button click & apply assertions + cy.wait("@revertApp") + cy.get("@revertApp").its('response.body').should('have.property', 'message', "App has not yet been deployed") + cy.get("@revertApp").its('response.body').should('have.property', 'status', 400) + }) + }) + + it("should revert a published app", () => { + // Add initial component - Paragraph + cy.addComponent("Elements", "Paragraph") + // Publish app + cy.get(".spectrum-Button").contains("Publish").click({ force: true }) + cy.get(".spectrum-ButtonGroup").within(() => { + cy.get(".spectrum-Button").contains("Publish").click({ force: true }) + }) + // Add second component - Button + cy.addComponent("Elements", "Button") + // Click Revert + cy.get(".toprightnav").within(() => { + cy.get(".spectrum-Icon").eq(1).click() + }) + cy.get(".spectrum-Dialog-grid").within(() => { + // Click Revert + cy.get(".spectrum-Button").contains("Revert").click({ force: true }) + cy.wait(1000) + }) + // Confirm Paragraph component is still visible + cy.get(".root").contains("New Paragraph") + // Confirm Button component is not visible + cy.get(".root").should("not.have.text", "New Button") + cy.wait(500) + }) + + it("should enter incorrect app name when reverting", () => { + // Click Revert + cy.get(".toprightnav").within(() => { + cy.get(".spectrum-Icon").eq(1).click({ force: true }) + }) + // Enter incorrect app name + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get("input").type("Cypress Tests") + // Revert button within modal should be disabled + cy.get(".spectrum-Button").eq(1).should('be.disabled') + }) + }) + }) +}) diff --git a/packages/builder/cypress/setup.js b/packages/builder/cypress/setup.js index ca5a65c7f5..e19c931ed9 100644 --- a/packages/builder/cypress/setup.js +++ b/packages/builder/cypress/setup.js @@ -4,17 +4,17 @@ const path = require("path") const tmpdir = path.join(require("os").tmpdir(), ".budibase") // normal development system -const WORKER_PORT = "10002" -const MAIN_PORT = cypressConfig.env.PORT +const SERVER_PORT = cypressConfig.env.PORT +const WORKER_PORT = cypressConfig.env.WORKER_PORT + process.env.BUDIBASE_API_KEY = "6BE826CB-6B30-4AEC-8777-2E90464633DE" process.env.NODE_ENV = "cypress" process.env.ENABLE_ANALYTICS = "false" -process.env.PORT = MAIN_PORT process.env.JWT_SECRET = cypressConfig.env.JWT_SECRET process.env.COUCH_URL = `leveldb://${tmpdir}/.data/` process.env.SELF_HOSTED = 1 -process.env.WORKER_URL = "http://localhost:10002/" -process.env.APPS_URL = `http://localhost:${MAIN_PORT}/` +process.env.WORKER_URL = `http://localhost:${WORKER_PORT}/` +process.env.APPS_URL = `http://localhost:${SERVER_PORT}/` process.env.MINIO_URL = `http://localhost:4004` process.env.MINIO_ACCESS_KEY = "budibase" process.env.MINIO_SECRET_KEY = "budibase" @@ -33,11 +33,14 @@ exports.run = ( // require("dotenv").config({ path: resolve(dir, ".env") }) // don't make this a variable or top level require // it will cause environment module to be loaded prematurely - require(serverLoc) + + // override the port with the worker port temporarily process.env.PORT = WORKER_PORT require(workerLoc) - // reload main port for rest of system - process.env.PORT = MAIN_PORT + + // override the port with the server port + process.env.PORT = SERVER_PORT + require(serverLoc) } if (require.main === module) { diff --git a/packages/builder/cypress/support/commands.js b/packages/builder/cypress/support/commands.js index d99e2b271f..699cf1fbf0 100644 --- a/packages/builder/cypress/support/commands.js +++ b/packages/builder/cypress/support/commands.js @@ -10,7 +10,7 @@ Cypress.on("uncaught:exception", () => { }) Cypress.Commands.add("login", () => { - cy.visit(`localhost:${Cypress.env("PORT")}/builder`) + cy.visit(`${Cypress.config().baseUrl}/builder`) cy.wait(2000) cy.url().then(url => { if (url.includes("builder/admin")) { @@ -33,37 +33,69 @@ Cypress.Commands.add("login", () => { }) Cypress.Commands.add("createApp", name => { - cy.visit(`localhost:${Cypress.env("PORT")}/builder`) + cy.visit(`${Cypress.config().baseUrl}/builder`) cy.wait(500) - cy.request(`${Cypress.config().baseUrl}api/applications?status=all`) - .its("body") - .then(body => { - if (body.length > 0) { - cy.get(".spectrum-Button").contains("Create app").click({ force: true }) - } - }) + cy.get(".spectrum-Button").contains("Create app").click({ force: true }) cy.get(".spectrum-Modal").within(() => { cy.get("input").eq(0).type(name).should("have.value", name).blur() cy.get(".spectrum-ButtonGroup").contains("Create app").click() - cy.wait(7000) + cy.wait(10000) }) + cy.createTable("Cypress Tests", true) }) -Cypress.Commands.add("deleteApp", appName => { - cy.visit(`localhost:${Cypress.env("PORT")}/builder`) - cy.wait(1000) - cy.request(`localhost:${Cypress.env("PORT")}/api/applications?status=all`) +Cypress.Commands.add("deleteApp", name => { + cy.visit(`${Cypress.config().baseUrl}/builder`) + cy.wait(2000) + cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`) .its("body") .then(val => { if (val.length > 0) { - cy.get( - ".appTable > :nth-child(5) > :nth-child(2) > .spectrum-Icon" - ).click() - cy.contains("Delete").click() - cy.get(".spectrum-Modal").within(() => { - cy.get("input").type(appName) - cy.get(".spectrum-Button--warning").click() + cy.searchForApplication(name) + cy.get(".appTable").within(() => { + cy.get(".spectrum-Icon").eq(1).click() }) + cy.get(".spectrum-Menu").then($menu => { + if ($menu.text().includes("Unpublish")) { + cy.get(".spectrum-Menu").contains("Unpublish").click() + cy.get(".spectrum-Dialog-grid").contains("Unpublish app").click() + } else { + cy.get(".spectrum-Menu").contains("Delete").click() + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get("input").type(name) + }) + cy.get(".spectrum-Button--warning").click() + } + }) + } else { + return + } + }) +}) + +Cypress.Commands.add("deleteAllApps", () => { + cy.visit(`${Cypress.config().baseUrl}/builder`) + cy.wait(500) + cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`) + .its("body") + .then(val => { + for (let i = 0; i < val.length; i++) { + cy.get(".spectrum-Heading") + .eq(1) + .then(app => { + const name = app.text() + cy.get(".title") + .children() + .within(() => { + cy.get(".spectrum-Icon").eq(0).click() + }) + cy.get(".spectrum-Menu").contains("Delete").click() + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get("input").type(name) + cy.get(".spectrum-Button--warning").click() + }) + cy.reload() + }) } }) }) @@ -72,6 +104,7 @@ Cypress.Commands.add("createTestApp", () => { const appName = "Cypress Tests" cy.deleteApp(appName) cy.createApp(appName, "This app is used for Cypress testing.") + cy.createScreen("home", "home") }) Cypress.Commands.add("createTestTableWithData", () => { @@ -80,10 +113,18 @@ Cypress.Commands.add("createTestTableWithData", () => { cy.addColumn("dog", "age", "Number") }) -Cypress.Commands.add("createTable", tableName => { - cy.contains("Budibase DB").click() - cy.contains("Create new table").click() - +Cypress.Commands.add("createTable", (tableName, initialTable) => { + if (!initialTable) { + cy.navigateToDataSection() + cy.get(`[data-cy="new-table"]`).click() + } + cy.wait(5000) + cy.get(".spectrum-Dialog-grid") + .contains("Budibase DB") + .click({ force: true }) + .then(() => { + cy.get(".spectrum-Button").contains("Continue").click({ force: true }) + }) cy.get(".spectrum-Modal").within(() => { cy.wait(1000) cy.get("input").first().type(tableName).blur() @@ -131,17 +172,19 @@ Cypress.Commands.add("addRow", values => { Cypress.Commands.add("addRowMultiValue", values => { cy.contains("Create row").click() - cy.get(".spectrum-Form-itemField") - .click() - .then(() => { - cy.get(".spectrum-Popover").within(() => { - for (let i = 0; i < values.length; i++) { - cy.get(".spectrum-Menu-item").eq(i).click() - } + cy.get(".spectrum-Modal").within(() => { + cy.get(".spectrum-Form-itemField") + .click() + .then(() => { + cy.get(".spectrum-Popover").within(() => { + for (let i = 0; i < values.length; i++) { + cy.get(".spectrum-Menu-item").eq(i).click() + } + }) + cy.get(".spectrum-Dialog-grid").click("top") + cy.get(".spectrum-ButtonGroup").contains("Create").click() }) - cy.get(".spectrum-Dialog-grid").click("top") - cy.get(".spectrum-ButtonGroup").contains("Create").click() - }) + }) }) Cypress.Commands.add("createUser", email => { @@ -190,22 +233,49 @@ Cypress.Commands.add("navigateToFrontend", () => { cy.wait(1000) cy.contains("Design").click() cy.get(".spectrum-Search").type("/") - cy.createScreen("home", "home") - cy.addComponent("Elements", "Headline") cy.get(".nav-item").contains("home").click() }) +Cypress.Commands.add("navigateToDataSection", () => { + // Clicks on the Data tab + cy.wait(500) + cy.contains("Data").click() +}) + Cypress.Commands.add("createScreen", (screenName, route) => { + cy.contains("Design").click() cy.get("[aria-label=AddCircle]").click() cy.get(".spectrum-Modal").within(() => { - cy.get(".item").first().click() - cy.get(".spectrum-Button--cta").click() + cy.get(".item").contains("Blank").click() + cy.get(".spectrum-Button").contains("Add screens").click({ force: true }) + cy.wait(500) }) + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Form-itemField").eq(0).type(screenName) + cy.get(".spectrum-Form-itemField").eq(1).type(route) + cy.get(".spectrum-Button").contains("Continue").click({ force: true }) + cy.wait(1000) + }) +}) + +Cypress.Commands.add("createAutogeneratedScreens", screenNames => { + // Screen name must already exist within data source + cy.contains("Design").click() + cy.get("[aria-label=AddCircle]").click() + for (let i = 0; i < screenNames.length; i++) { + cy.get(".item").contains(screenNames[i]).click() + } + cy.get(".spectrum-Button").contains("Add screens").click({ force: true }) + cy.wait(4000) +}) + +Cypress.Commands.add("addRow", values => { + cy.contains("Create row").click() cy.get(".spectrum-Modal").within(() => { - cy.get("input").first().clear().type(screenName) - cy.get("input").eq(1).clear().type(route) - cy.get(".spectrum-Button--cta").click() - cy.wait(2000) + for (let i = 0; i < values.length; i++) { + cy.get("input").eq(i).type(values[i]).blur() + } + cy.get(".spectrum-ButtonGroup").contains("Create").click() }) }) @@ -243,7 +313,144 @@ Cypress.Commands.add("addCustomSourceOptions", totalOptions => { }) Cypress.Commands.add("searchForApplication", appName => { - cy.get(".spectrum-Textfield").within(() => { - cy.get("input").eq(0).type(appName) + cy.wait(1000) + // Searches for the app + cy.get(".filter").then(() => { + cy.get(".spectrum-Textfield").within(() => { + cy.get("input").eq(0).type(appName) + }) + }) + // Confirms app exists after search + cy.get(".appTable").contains(appName) +}) + +Cypress.Commands.add("selectExternalDatasource", datasourceName => { + // Navigates to Data Section + cy.navigateToDataSection() + // Open Data Source modal + cy.get(".nav").within(() => { + cy.get(".add-button").click() + }) + // Clicks specified datasource & continue + cy.get(".item-list").contains(datasourceName).click() + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Button").contains("Continue").click({ force: true }) }) }) + +Cypress.Commands.add("addDatasourceConfig", (datasource, skipFetch) => { + // selectExternalDatasource should be called prior to this + // Adds the config for specified datasource & fetches tables + // Currently supports MySQL, PostgreSQL, Oracle + // Host IP Address + cy.wait(500) + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".form-row") + .eq(0) + .within(() => { + cy.get(".spectrum-Textfield").within(() => { + if (datasource == "Oracle") { + cy.get("input").clear().type(Cypress.env("oracle").HOST) + } else { + cy.get("input").clear().type(Cypress.env("HOST_IP")) + } + }) + }) + }) + // Database Name + cy.get(".spectrum-Dialog-grid").within(() => { + if (datasource == "MySQL") { + cy.get(".form-row") + .eq(4) + .within(() => { + cy.get("input").clear().type(Cypress.env("mysql").DATABASE) + }) + } else { + cy.get(".form-row") + .eq(2) + .within(() => { + if (datasource == "PostgreSQL") { + cy.get("input").clear().type(Cypress.env("postgresql").DATABASE) + } + if (datasource == "Oracle") { + cy.get("input").clear().type(Cypress.env("oracle").DATABASE) + } + }) + } + }) + // User + cy.get(".spectrum-Dialog-grid").within(() => { + if (datasource == "MySQL") { + cy.get(".form-row") + .eq(2) + .within(() => { + cy.get("input").clear().type(Cypress.env("mysql").USER) + }) + } else { + cy.get(".form-row") + .eq(3) + .within(() => { + if (datasource == "PostgreSQL") { + cy.get("input").clear().type(Cypress.env("postgresql").USER) + } + if (datasource == "Oracle") { + cy.get("input").clear().type(Cypress.env("oracle").USER) + } + }) + } + }) + // Password + cy.get(".spectrum-Dialog-grid").within(() => { + if (datasource == "MySQL") { + cy.get(".form-row") + .eq(3) + .within(() => { + cy.get("input").clear().type(Cypress.env("mysql").PASSWORD) + }) + } else { + cy.get(".form-row") + .eq(4) + .within(() => { + if (datasource == "PostgreSQL") { + cy.get("input").clear().type(Cypress.env("postgresql").PASSWORD) + } + if (datasource == "Oracle") { + cy.get("input").clear().type(Cypress.env("oracle").PASSWORD) + } + }) + } + }) + // Click to fetch tables + if (skipFetch) { + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Button") + .contains("Skip table fetch") + .click({ force: true }) + }) + } else { + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Button") + .contains("Save and fetch tables") + .click({ force: true }) + cy.wait(1000) + }) + } +}) + +Cypress.Commands.add("createRestQuery", (method, restUrl, queryPrettyName) => { + // addExternalDatasource should be called prior to this + // Configures REST datasource & sends query + cy.wait(1000) + cy.get(".spectrum-Button").contains("Add query").click({ force: true }) + // Select Method & add Rest URL + cy.get(".spectrum-Picker-label").eq(1).click() + cy.get(".spectrum-Menu").contains(method).click() + cy.get("input").clear().type(restUrl) + // Send query + cy.get(".spectrum-Button").contains("Send").click({ force: true }) + cy.wait(500) + cy.get(".spectrum-Button").contains("Save").click({ force: true }) + cy.get(".hierarchy-items-container") + .should("contain", method) + .and("contain", queryPrettyName) +}) diff --git a/packages/builder/cypress/support/filterTests.js b/packages/builder/cypress/support/filterTests.js new file mode 100644 index 0000000000..074fd05d33 --- /dev/null +++ b/packages/builder/cypress/support/filterTests.js @@ -0,0 +1,16 @@ +const filterTests = (testTags, runTest) => { + // testTags is an array of tags + // runTest is all tests + if (Cypress.env("TEST_TAGS")) { + const tags = Cypress.env("TEST_TAGS").split("/") + const found = testTags.some($testTags => tags.includes($testTags)) + + if (found) { + runTest() + } + } else { + runTest() + } +} + +export default filterTests diff --git a/packages/builder/cypress/support/queryLevelTransformerFunction.js b/packages/builder/cypress/support/queryLevelTransformerFunction.js new file mode 100644 index 0000000000..7dc05018f8 --- /dev/null +++ b/packages/builder/cypress/support/queryLevelTransformerFunction.js @@ -0,0 +1,14 @@ +/* eslint-disable */ +const breweries = data +const totals = {} + +for (let brewery of breweries) + {const state = brewery.state + if (totals[state] == null) + {totals[state] = 1 + } else + {totals[state]++ + } +} +const entries = Object.entries(totals) +return entries.map(([state, count]) => ({ state, count })) diff --git a/packages/builder/cypress/support/queryLevelTransformerFunctionWithData.js b/packages/builder/cypress/support/queryLevelTransformerFunctionWithData.js new file mode 100644 index 0000000000..fcf50b4412 --- /dev/null +++ b/packages/builder/cypress/support/queryLevelTransformerFunctionWithData.js @@ -0,0 +1,31 @@ +/* eslint-disable */ +const breweries = data +const totals = {} +for (let brewery of breweries) + {const state = brewery.state + if (totals[state] == null) + {totals[state] = 1 + } else + {totals[state]++ + } +} +const stateCodes = + {texas: "tx", + colorado: "co", + florida: "fl", + iwoa: "ia", + louisiana: "la", + california: "ca", + pennsylvania: "pa", + georgia: "ga", + "new hampshire": "nh", + virginia: "va", + michigan: "mi", + maryland: "md", + ohio: "oh", +} +const entries = Object.entries(totals) +return entries.map(([state, count]) => + {stateCodes[state.toLowerCase()] + return { state, count, flag: "http://flags.ox3.in/svg/us/${stateCode}.svg" } +}) diff --git a/packages/builder/nuxt.config.js b/packages/builder/nuxt.config.js new file mode 100644 index 0000000000..72798a820c --- /dev/null +++ b/packages/builder/nuxt.config.js @@ -0,0 +1,4 @@ +export default { + ssr: false, + target: "static", +} diff --git a/packages/builder/package.json b/packages/builder/package.json index dbfbdfe0f2..7b9f2fde9e 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "1.0.74-alpha.0", + "version": "1.0.98-alpha.1", "license": "GPL-3.0", "private": true, "scripts": { @@ -11,12 +11,13 @@ "rollup": "rollup -c -w", "cy:setup": "ts-node ./cypress/ts/setup.ts", "cy:setup:ci": "node ./cypress/setup.js", - "cy:run": "cypress run", "cy:open": "cypress open", - "cy:run:ci": "cypress run --record", - "cy:test": "start-server-and-test cy:setup http://localhost:10001/builder cy:run", - "cy:ci": "start-server-and-test cy:setup:ci http://localhost:10001/builder cy:run", - "cy:debug": "start-server-and-test cy:setup http://localhost:10001/builder cy:open" + "cy:run": "cypress run", + "cy:run:ci": "xvfb-run cypress run --headed --browser chrome", + "cy:test": "start-server-and-test cy:setup http://localhost:4100/builder cy:run", + "cy:ci": "start-server-and-test cy:setup:ci http://localhost:4100/builder cy:run:ci", + "cy:debug": "start-server-and-test cy:setup http://localhost:4100/builder cy:open", + "cy:debug:ci": "start-server-and-test cy:setup:ci http://localhost:4100/builder cy:open" }, "jest": { "globals": { @@ -64,10 +65,10 @@ } }, "dependencies": { - "@budibase/bbui": "^1.0.74-alpha.0", - "@budibase/client": "^1.0.74-alpha.0", - "@budibase/frontend-core": "^1.0.74-alpha.0", - "@budibase/string-templates": "^1.0.74-alpha.0", + "@budibase/bbui": "^1.0.98-alpha.1", + "@budibase/client": "^1.0.98-alpha.1", + "@budibase/frontend-core": "^1.0.98-alpha.1", + "@budibase/string-templates": "^1.0.98-alpha.1", "@sentry/browser": "5.19.1", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", @@ -94,7 +95,7 @@ "@testing-library/jest-dom": "^5.11.10", "@testing-library/svelte": "^3.0.0", "babel-jest": "^26.6.3", - "cypress": "^5.1.0", + "cypress": "^9.3.1", "cypress-terminal-report": "^1.4.1", "identity-obj-proxy": "^3.0.0", "jest": "^26.6.3", diff --git a/packages/builder/src/App.svelte b/packages/builder/src/App.svelte index 60051ea043..0fb0fe59d5 100644 --- a/packages/builder/src/App.svelte +++ b/packages/builder/src/App.svelte @@ -1,13 +1,16 @@ +