diff --git a/.dockerignore b/.dockerignore index 2cb7fe042..e5cedb1b8 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,11 +1,48 @@ .editorconfig +.env .env.example +.env.staging +.git .github +.nx .vscode .workspace.code-workspace app -# dist +apps/landing/.next +dist electron-scripts logs node_modules npm-debug.log +test-results +tmp +yarn-error.log + +# System Files +.DS_Store +Thumbs.db + +# Generated Docusaurus files +.docusaurus/ +.cache-loader/ + +# Next.js +.next + +# Playwright +**/test-results +**/playwright-report +**/playwright/.cache +.nx + +# Not required for core jetstream +apps/cron-tasks +apps/docs +apps/electron +apps/jetstream-e2e +electron-scripts +*# +*~ +.DS_Store +Thumbs.db + diff --git a/.env.example b/.env.example index 76dd5ec74..85a470ffe 100644 --- a/.env.example +++ b/.env.example @@ -22,7 +22,7 @@ SFDC_CONSUMER_KEY='3MVG9tSqyyAXNH5ItQtuplEg40Ks_MLSG37L1PV.TLDjsCbdp7EDonFUW0csS SFDC_CONSUMER_SECRET='F77C1B4AF03CF51B290A591766F4C430E3136949A636D4AA5339F8EB6A40052A' # API VERSION TO USE -NX_SFDC_API_VERSION='55.0' +SFDC_API_VERSION='58.0' # If set to true, then authentication will be bypassed # You will use a test account instead of a real account - only works if running locally @@ -45,8 +45,6 @@ PRISMA_DEBUG='false' NX_SFDC_CLIENT_ID_ELECTRON='' NX_AUTH_AUDIENCE='http://getjetstream.app/app_metadata' - -NX_AG_GRID_KEY='' NX_ROLLBAR_KEY='' NX_AMPLITUDE_KEY='' @@ -72,8 +70,8 @@ GOOGLE_ENC_KEY='' ROLLBAR_SERVER_TOKEN='' # Algolia API key - used to index docs pages -APPLICATION_ID='' -API_KEY='' +ALGOLIA_APPLICATION_ID='' +ALGOLIA_API_KEY='' # HONEYCOMBE - server analytics, only set to true on hosted server HONEYCOMB_ENABLED=false diff --git a/.eslintrc.json b/.eslintrc.json index 1b571356d..3df01e53b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -41,13 +41,17 @@ { "files": ["*.tsx"], "rules": { - "@typescript-eslint/no-unused-vars": "off" + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-unused-vars": "warn" } }, { "files": ["*.ts", "*.tsx"], "extends": ["plugin:@nrwl/nx/typescript"], - "rules": {} + "rules": { + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-unused-vars": "warn" + } }, { "files": ["*.js", "*.jsx"], diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 69784e042..0548c3682 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 name: Checkout [master] with: fetch-depth: 0 @@ -30,21 +30,23 @@ jobs: run: git branch --track main origin/main - name: Derive appropriate SHAs for base and head for `nx affected` commands - uses: nrwl/nx-set-shas@v3 + uses: nrwl/nx-set-shas@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: - node-version: '18' + node-version: '20' cache: 'yarn' - name: install dependencies - run: yarn install --frozen-lockfile --prefer-offline + run: yarn install --frozen-lockfile - name: Build application + env: + NODE_OPTIONS: '--max_old_space_size=4096' run: npx nx run-many --target=build --parallel=3 --projects=jetstream,api,download-zip-sw,landing --configuration=production - name: Uploading artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: dist-artifacts path: dist @@ -78,7 +80,7 @@ jobs: SFDC_CONSUMER_KEY: ${{ secrets.SFDC_CONSUMER_KEY }} SFDC_CONSUMER_SECRET: ${{ secrets.SFDC_CONSUMER_SECRET }} SFDC_ENC_KEY: ${{ secrets.SFDC_ENC_KEY }} - SFDC_API_VERSION: '57.0' + SFDC_API_VERSION: '58.0' services: postgres: @@ -96,21 +98,21 @@ jobs: - 5432:5432 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 name: Checkout [master] with: fetch-depth: 0 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: - node-version: '18' + node-version: '20' cache: 'yarn' - name: install dependencies - run: yarn install --frozen-lockfile --prefer-offline + run: yarn install --frozen-lockfile - name: Download artifacts from build - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: dist-artifacts path: dist @@ -139,7 +141,7 @@ jobs: - name: Upload test results if: always() # This ensures step will always run even if prior steps fail - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: playwright-report path: | diff --git a/.github/workflows/deployment-monitor.yml b/.github/workflows/deployment-monitor.yml new file mode 100644 index 000000000..ee72fce62 --- /dev/null +++ b/.github/workflows/deployment-monitor.yml @@ -0,0 +1,21 @@ +name: Deployment +on: + push: + branches: + - release + +jobs: + deploy: + name: Wait for Deploy + runs-on: ubuntu-latest + steps: + # https://github.com/marketplace/actions/render-github-action + - name: Wait for Render Deployment + uses: bounceapp/render-action@0.6.0 + with: + render-token: ${{ secrets.RENDER_TOKEN }} + github-token: ${{ secrets.GITHUB_TOKEN }} + service-id: srv-cm55msocmk4c73cnddi0 + # retries: 20 + # wait: 16000 + # sleep: 30000 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c963ae67c..e4c610901 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,7 +23,7 @@ jobs: - name: Init npm cache uses: actions/setup-node@v3 with: - node-version: '18' + node-version: '20' cache: 'yarn' - name: install dependencies run: yarn install --frozen-lockfile diff --git a/.gitignore b/.gitignore index 04ac4f4d3..e340afc3c 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,5 @@ package-lock.json **/test-results **/playwright-report **/playwright/.cache + +.nx/cache \ No newline at end of file diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 000000000..209e3ef4b --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20 diff --git a/.prettierignore b/.prettierignore index 8237da597..80a9cf177 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,3 +3,5 @@ /dist /coverage .docusaurus/ + +/.nx/cache \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 5677f0393..b736e95b4 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -8,6 +8,7 @@ "streetsidesoftware.code-spell-checker", "eamodio.gitlens", "ms-playwright.playwright", - "wayou.vscode-todo-highlight" + "wayou.vscode-todo-highlight", + "firsttris.vscode-jest-runner" ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index f09b82ce0..7aae96ff5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,7 +3,7 @@ "editor.tabSize": 2, "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.organizeImports": true + "source.organizeImports": "explicit" }, "files.exclude": { "**/.git": true, diff --git a/Dockerfile b/Dockerfile index c049bb1ab..25a88e40e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,41 +1,57 @@ -# docker build -f Dockerfile . -t jetstream -# docker-compose up +# syntax = docker/dockerfile:1 -# Login and run DB migrations (TODO: figure out how to automate this) -# https://medium.com/@sumankpaul/run-db-migration-script-in-docker-compose-ce8e447a77ba -# docker ps -# docker exec -it 791 bash -# npx prisma migrate deploy +ARG NODE_VERSION=20.10.0 +ARG ENVIRONMENT=production -# TODO: auth redirect flow is broken, need to fix it +FROM node:${NODE_VERSION}-slim as base -FROM node:16 +# App lives here +WORKDIR /app -WORKDIR /usr/src/app +# Set production environment +ENV NODE_ENV=production +ARG YARN_VERSION=1.22.21 +RUN npm install -g yarn@$YARN_VERSION --force -# Copy application -COPY ./dist/apps/api ./dist/apps/api/ -COPY ./dist/apps/jetstream ./dist/apps/jetstream/ -COPY ./dist/apps/download-zip-sw ./dist/apps/download-zip-sw/ -COPY ./dist/apps/landing ./dist/apps/landing/ +# Throw-away build stage to reduce size of final image +FROM base as build -# Copy supporting files -COPY ./dist/apps/api/package.json . -COPY ./yarn.lock . -COPY ./.env . -COPY ./ecosystem.config.js . -COPY ./prisma ./prisma/ +# Install packages needed to build node modules +RUN apt-get update -qq && \ + apt-get install --no-install-recommends -y build-essential node-gyp openssl pkg-config python-is-python3 -# Install core dependencies -RUN yarn +# Install node modules +COPY --link package.json yarn.lock ./ +RUN yarn install --frozen-lockfile --production=false -# Install other dependencies that were not calculated by nx, but are required -RUN yarn add dotenv prisma@^3.13.0 +# Generate Prisma Client +COPY --link prisma . +RUN yarn run db:generate -# Generate prisma client - ensure that there are no OS differences -RUN npx prisma generate +# Copy application code +COPY --link . . -EXPOSE 3333 -EXPOSE 9229 +# Build application +RUN yarn build:core +RUN yarn build:landing + +# Remove development dependencies +RUN yarn install --production=true + + +# Final stage for app image +FROM base -CMD [ "node", "--inspect=0.0.0.0", "dist/apps/api/main.js" ] +# Install packages needed for deployment +RUN apt-get update -qq && \ + apt-get install --no-install-recommends -y openssl && \ + rm -rf /var/lib/apt/lists /var/cache/apt/archives + +RUN npm install -g ts-node@10.9.1 + +# Copy built application +COPY --from=build /app /app + +# Start the server by default, this can be overwritten at runtime +EXPOSE 3333 +CMD [ "yarn", "run", "start:prod" ] diff --git a/Dockerfile.db-migration b/Dockerfile.db-migration deleted file mode 100644 index 154490ace..000000000 --- a/Dockerfile.db-migration +++ /dev/null @@ -1,11 +0,0 @@ -# Runs database migrations -FROM node:16 - -WORKDIR /usr/src/app - -COPY ./prisma ./prisma/ - -RUN yarn add prisma - -# Generate prisma client - ensure that there are no OS differences -CMD [ "npx", "prisma", "migrate", "deploy" ] diff --git a/Dockerfile.e2e b/Dockerfile.e2e index 581cfc1ef..8e3b1eaf3 100644 --- a/Dockerfile.e2e +++ b/Dockerfile.e2e @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/playwright:v1.27.0-focal +FROM mcr.microsoft.com/playwright:v1.36.0 WORKDIR /usr/src/app diff --git a/LICENSE.md b/LICENSE.md index 934ab1d9f..e84f1499d 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,164 +1,11 @@ -GNU LESSER GENERAL PUBLIC LICENSE -Version 3, 29 June 2007 +“Commons Clause” License Condition v1.0 -Copyright (C) 2007 Free Software Foundation, Inc. -Everyone is permitted to copy and distribute verbatim copies -of this license document, but changing it is not allowed. +The Software is provided to you by the Licensor under the License, as defined below, subject to the following condition. -This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. +Without limiting other conditions in the License, the grant of rights under the License will not include, and the License does not grant to you, right to Sell the Software. -0. Additional Definitions. +For purposes of the foregoing, “Sell” means practicing any or all of the rights granted to you under the License to provide to third parties, for a fee or other consideration (including without limitation fees for hosting or consulting/ support services related to the Software), a product or service whose value derives, entirely or substantially, from the functionality of the Software. Any license notice or attribution required by the License must also include this Commons Cause License Condition notice. -As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - -"The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - -An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - -A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - -The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - -The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - -1. Exception to Section 3 of the GNU GPL. - -You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - -2. Conveying Modified Versions. - -If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - -a) under this License, provided that you make a good faith effort to -ensure that, in the event an Application does not supply the -function or data, the facility still operates, and performs -whatever part of its purpose remains meaningful, or - -b) under the GNU GPL, with none of the additional permissions of -this License applicable to that copy. - -3. Object Code Incorporating Material from Library Header Files. - -The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - -a) Give prominent notice with each copy of the object code that the -Library is used in it and that the Library and its use are -covered by this License. - -b) Accompany the object code with a copy of the GNU GPL and this license -document. - -4. Combined Works. - -You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - -a) Give prominent notice with each copy of the Combined Work that -the Library is used in it and that the Library and its use are -covered by this License. - -b) Accompany the Combined Work with a copy of the GNU GPL and this license -document. - -c) For a Combined Work that displays copyright notices during -execution, include the copyright notice for the Library among -these notices, as well as a reference directing the user to the -copies of the GNU GPL and this license document. - -d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - -e) Provide Installation Information, but only if you would otherwise -be required to provide such information under section 6 of the -GNU GPL, and only to the extent that such information is -necessary to install and execute a modified version of the -Combined Work produced by recombining or relinking the -Application with a modified version of the Linked Version. (If -you use option 4d0, the Installation Information must accompany -the Minimal Corresponding Source and Corresponding Application -Code. If you use option 4d1, you must provide the Installation -Information in the manner specified by section 6 of the GNU GPL -for conveying Corresponding Source.) - -5. Combined Libraries. - -You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - -a) Accompany the combined library with a copy of the same work based -on the Library, uncombined with any other library facilities, -conveyed under the terms of this License. - -b) Give prominent notice with the combined library that part of it -is a work based on the Library, and explaining where to find the -accompanying uncombined form of the same work. - -6. Revised Versions of the GNU Lesser General Public License. - -The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - -If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. +Software: Jetstream +License: Apache 2.0 +Licensor: Jetstream Solutions, LLC diff --git a/README.md b/README.md index 6e02e6c7a..54b1457f7 100644 --- a/README.md +++ b/README.md @@ -8,20 +8,20 @@ The Jetstream platform makes managing your Salesforce instances a breeze. Use Je Learn more by [reading the docs](https://docs.getjetstream.app/). -**JETSTREAM IS OPEN SOURCE AND FREE TO USE. IF YOUR COMPANY IS GETTING VALUE, PLEASE CONSIDER SPONSORING THE PROJECT ❤️** +**JETSTREAM IS SOURCE-AVAILABLE AND FREE TO USE. IF YOUR COMPANY IS GETTING VALUE, CONSIDER SPONSORING THE PROJECT ❤️** + +Jetstream wouldn't be possible without your contributions. [![](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/jetstreamapp) There are multiple ways to use Jetstream. 1. Use the hosted version at https://getjetstream.app -2. Use the desktop version **TODO: COMING SOON** -3. Run locally +2. Run locally 1. Using nodejs 1. Building yourself (recommended if you want to contribute to the Jetstream codebase) - 2. Using the pre-built version **TODO: COMING SOON** 2. Using Docker -4. Want to self-host behind your company firewall? Reach out to the team for assistance. +3. Want to self-host behind your company firewall? Reach out to the team for assistance. # Overview of the codebase structure @@ -42,7 +42,6 @@ This project was generated using [Nx](https://nx.dev) - This repository is consi │ ├── jetstream-e2e │ ├── jetstream-worker │ ├── landing (LANDING PAGE WEBSITE) -│ ├── landing-e2e │ ├── maizzle (EMAIL TEMPLATE GENERATION) │ └── ui-e2e ├── build (DESKTOP BUILD) @@ -74,13 +73,39 @@ This project was generated using [Nx](https://nx.dev) - This repository is consi **Pre-req** -1. Make sure you have node 16 or 18 installed. -2. If you want to run the dev server, make sure you have yarn installed. +1. Make sure you have node 20 installed. +2. If you are using docker, make sure you have Docker installed. +3. If you want to run the dev server, make sure you have yarn version 1 installed. 📓 You can choose to skip authentication locally by setting the environment variable `EXAMPLE_USER_OVERRIDE=true`. This is set to true by default in the `.env.example` file. 🌟 To use this, don't click the login button, but instead just go to `http://localhost:3333/app` or `http://localhost:4200/app` (if running the react development server) directly. -The easiest way to run Jetstream locally is to download the pre-built and transpiled javascript files and run them using NodeJs. +### Using Docker + +If you have docker and just want to run the application locally, using docker is the easiest option. + +Build the docker image (this takes a while the first time). + +```shell +docker build -t jetstream-app . +``` + +Use docker compose to create a dockerized postgres database and run the app. + +```shell +docker compose up +``` + +- Jetstream will be running at `http://localhost:3333` +- Postgres will be running on port `5555` if you wanted to connect to it locally. +- When you click "Login", you should immediately be logged in without having to sign in. + - You can set `EXAMPLE_USER_OVERRIDE` if you want to disable this behavior +- If assets on the page don't load, do a hard refresh (hold cmd or shift and press refresh) + - This might happen if you have re-built the image and the browser has cached the page with now missing resources. + +### Running without Docker + +Use this option if you want to contribute to the codebase. Jetstream relies on a Postgres database, so you either need to [run Postgresql locally](https://www.postgresql.org/download/) or use a managed provider such as one from the list below. Optionally you can run jetstream in a Docker container which includes Postgresql. @@ -107,12 +132,6 @@ If you want to create your own: 3. All other defaults are fine 3. Update the file named `.env` and replace `SFDC_CONSUMER_KEY` and `SFDC_CONSUMER_SECRET` with the values from your connected app. -### Download pre-built application - -This is the fastest 🏃 way to run Jetstream locally. - -TODO: instructions to download and instructions to run - ### Building ⭐ If you want to contribute to Jetstream, this is the best option. @@ -167,7 +186,7 @@ TODO: instructions to download and instructions to run ## Desktop Application -**TODO: THIS HAS NOT BEEN ENTIRELY WORKED OUT YET** +This is a work in progress and may be removed as an available option. ### Local development diff --git a/apps/api/.env.development b/apps/api/.env.development new file mode 100644 index 000000000..49b79c58a --- /dev/null +++ b/apps/api/.env.development @@ -0,0 +1,21 @@ +ENVIRONMENT="development" + +AUTH0_DOMAIN="getjetstream-dev.us.auth0.com" +AUTH0_M2M_DOMAIN="getjetstream-dev.us.auth0.com" + +CONTENTFUL_HOST="https://api.contentful.com" + +GOOGLE_REDIRECT_URI="http://localhost:3333/oauth/google/callback" + +HONEYCOMB_ENABLED=false + +JETSTREAM_CLIENT_URL="http://localhost:4200/app" +JETSTREAM_SERVER_DOMAIN="localhost:3333" +JETSTREAM_SERVER_URL="http://localhost:3333" + +NX_AUTH_AUDIENCE="http://getjetstream.app/app_metadata" +NX_BRANCH="main" +NX_SFDC_API_VERSION="60.0" + +SFDC_API_VERSION="60.0" +SFDC_CALLBACK_URL="http://localhost:3333/oauth/sfdc/callback" diff --git a/apps/api/.env.production b/apps/api/.env.production new file mode 100644 index 000000000..19a14262a --- /dev/null +++ b/apps/api/.env.production @@ -0,0 +1,21 @@ +ENVIRONMENT="production" + +AUTH0_DOMAIN="auth.getjetstream.app" +AUTH0_M2M_DOMAIN="getjetstream.us.auth0.com" + +CONTENTFUL_HOST="cdn.contentful.com" + +GOOGLE_REDIRECT_URI="https://getjetstream.app/oauth/google/callback" + +HONEYCOMB_ENABLED=true + +JETSTREAM_CLIENT_URL="https://getjetstream.app/app" +JETSTREAM_SERVER_DOMAIN="getjetstream.app" +JETSTREAM_SERVER_URL="https://getjetstream.app" + +NX_AUTH_AUDIENCE="http://getjetstream.app/app_metadata" +NX_BRANCH="main" +NX_SFDC_API_VERSION="59.0" + +SFDC_API_VERSION="59.0" +SFDC_CALLBACK_URL="https://getjetstream.app/oauth/sfdc/callback" diff --git a/apps/api/.eslintrc.json b/apps/api/.eslintrc.json index 1df99e54c..9d9c0db55 100644 --- a/apps/api/.eslintrc.json +++ b/apps/api/.eslintrc.json @@ -1,13 +1,9 @@ { - "extends": "../../.eslintrc.json", - "rules": {}, + "extends": ["../../.eslintrc.json"], "ignorePatterns": ["!**/*"], "overrides": [ { "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], - "parserOptions": { - "project": ["apps/api/tsconfig.*?.json"] - }, "rules": {} }, { diff --git a/apps/api/Dockerfile b/apps/api/Dockerfile new file mode 100644 index 000000000..ec590fa5a --- /dev/null +++ b/apps/api/Dockerfile @@ -0,0 +1,24 @@ +# This file is generated by Nx. +# +# Build the docker image with `npx nx docker-build api`. +# Tip: Modify "docker-build" options in project.json to change docker build args. +# +# Run the container with `docker run -p 3333:3333 -t api`. +FROM docker.io/node:lts-alpine + +ENV HOST=0.0.0.0 +ENV PORT=3333 + +WORKDIR /app + +RUN addgroup --system api && \ + adduser --system -G api api + +COPY dist/apps/api api +RUN chown -R api:api . + +# You can remove this install step if you build with `--bundle` option. +# The bundled output will include external dependencies. +RUN npm --prefix server --omit=dev -f install + +CMD [ "node", "server" ] diff --git a/apps/api/jest.config.ts b/apps/api/jest.config.ts index cd65436e0..6537324d2 100644 --- a/apps/api/jest.config.ts +++ b/apps/api/jest.config.ts @@ -1,8 +1,11 @@ /* eslint-disable */ export default { - coverageDirectory: '../../coverage/apps/api', - globals: {}, - displayName: 'api', - testEnvironment: 'node', + displayName: 'server', preset: '../../jest.preset.js', + testEnvironment: 'node', + transform: { + '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../coverage/apps/server', }; diff --git a/apps/api/project.json b/apps/api/project.json index 120b201fc..00bd07d06 100644 --- a/apps/api/project.json +++ b/apps/api/project.json @@ -3,82 +3,95 @@ "$schema": "../../node_modules/nx/schemas/project-schema.json", "sourceRoot": "apps/api/src", "projectType": "application", - "prefix": "api", - "generators": {}, "targets": { "build": { - "executor": "@nrwl/webpack:webpack", + "executor": "@nx/esbuild:esbuild", + "outputs": ["{options.outputPath}"], + "defaultConfiguration": "production", "options": { + "platform": "node", "outputPath": "dist/apps/api", + "format": ["cjs"], + "bundle": true, "main": "apps/api/src/main.ts", "tsConfig": "apps/api/tsconfig.app.json", - "assets": ["apps/api/src/assets"], + "assets": [ + { + "glob": "**/*", + "input": "apps/api/src/assets", + "output": "assets", + "ignore": [".gitkeep"] + } + ], "generatePackageJson": true, - "webpackConfig": "webpack-server.config.js", - "target": "node", - "compiler": "tsc" + "sourcemap": true, + "esbuildOptions": { + "sourcemap": true, + "outExtension": { + ".js": ".js" + } + } }, "configurations": { + "development": { + "inspect": true + }, "production": { - "optimization": true, - "extractLicenses": true, - "inspect": false, - "sourceMap": true, - "fileReplacements": [ + "assets": [ { - "replace": "apps/api/src/environments/environment.ts", - "with": "apps/api/src/environments/environment.prod.ts" + "glob": "**/*", + "input": "apps/api/src/assets", + "output": "assets", + "ignore": [".gitkeep"] } - ] - }, - "docker": { - "optimization": false, - "extractLicenses": false, - "inspect": true, - "sourceMap": true, - "fileReplacements": [ - { - "replace": "apps/api/src/environments/environment.ts", - "with": "apps/api/src/environments/environment.prod.ts" + ], + "esbuildOptions": { + "sourcemap": true, + "outExtension": { + ".js": ".js" } - ] - }, - "test": { - "optimization": false, - "extractLicenses": false, - "inspect": true, + }, "fileReplacements": [ { "replace": "apps/api/src/environments/environment.ts", - "with": "apps/api/src/environments/environment.test.ts" + "with": "apps/api/src/environments/environment.prod.ts" } ] } - }, - "outputs": ["{options.outputPath}"] + } }, "serve": { - "executor": "@nrwl/node:node", + "executor": "@nx/js:node", + "defaultConfiguration": "development", "options": { "buildTarget": "api:build", - "inspect": true, + "inspect": "inspect", "port": 7777 + }, + "configurations": { + "development": { + "buildTarget": "api:build:development" + }, + "production": { + "buildTarget": "api:build:production" + } } }, "lint": { - "executor": "@nrwl/linter:eslint", - "options": { - "lintFilePatterns": ["apps/api/**/*.ts", "apps/api/**/*.spec.ts", "apps/api/**/*.d.ts"] - } + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"] }, "test": { - "executor": "@nrwl/jest:jest", + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], "options": { - "jestConfig": "apps/api/jest.config.ts", - "passWithNoTests": true - }, - "outputs": ["{workspaceRoot}/coverage/apps/api"] + "jestConfig": "apps/api/jest.config.ts" + } + }, + "docker-build": { + "dependsOn": ["build"], + "command": "docker build -f apps/api/Dockerfile . -t api" } }, - "tags": [] + "tags": ["server"] } diff --git a/apps/api/src/app/controllers/auth.controller.ts b/apps/api/src/app/controllers/auth.controller.ts index b54f8fb55..4671cca49 100644 --- a/apps/api/src/app/controllers/auth.controller.ts +++ b/apps/api/src/app/controllers/auth.controller.ts @@ -30,17 +30,17 @@ export async function login(req: Request, res: Response) { const user = req.user as UserProfileServer; req.logIn(user, async (err) => { if (err) { - logger.warn('[AUTH][ERROR] Error logging in %o', err); + logger.warn('[AUTH][ERROR] Error logging in %o', err, { requestId: res.locals.requestId }); return res.redirect('/'); } createOrUpdateUser(user) .then(async ({ user: _user }) => { - logger.info('[AUTH][SUCCESS] Logged in %s', _user.email, { userId: user.id }); + logger.info('[AUTH][SUCCESS] Logged in %s', _user.email, { userId: user.id, requestId: res.locals.requestId }); res.redirect(ENV.JETSTREAM_CLIENT_URL!); }) .catch((err) => { - logger.error('[AUTH][DB][ERROR] Error creating or sending welcome email %o', err); + logger.error('[AUTH][DB][ERROR] Error creating or sending welcome email %o', err, { requestId: res.locals.requestId }); res.redirect('/'); }); }); @@ -59,28 +59,28 @@ export async function callback(req: Request, res: Response, next: NextFunction) }, (err, user, info) => { if (err) { - logger.warn('[AUTH][ERROR] Error with authentication %o', err); + logger.warn('[AUTH][ERROR] Error with authentication %o', err, { requestId: res.locals.requestId }); return next(new AuthenticationError(err)); } if (!user) { - logger.warn('[AUTH][ERROR] no user'); - logger.warn('[AUTH][ERROR] no info %o', info); + logger.warn('[AUTH][ERROR] no user', { requestId: res.locals.requestId }); + logger.warn('[AUTH][ERROR] no info %o', info, { requestId: res.locals.requestId }); return res.redirect('/oauth/login'); } req.logIn(user, async (err) => { if (err) { - logger.warn('[AUTH][ERROR] Error logging in %o', err); + logger.warn('[AUTH][ERROR] Error logging in %o', err, { requestId: res.locals.requestId }); return next(new AuthenticationError(err)); } createOrUpdateUser(user).catch((err) => { - logger.error('[AUTH][DB][ERROR] Error creating or sending welcome email %o', err); + logger.error('[AUTH][DB][ERROR] Error creating or sending welcome email %o', err, { requestId: res.locals.requestId }); }); // TODO: confirm returnTo 0 it suddenly was reported as bad const returnTo = (req.session as any).returnTo; delete (req.session as any).returnTo; - logger.info('[AUTH][SUCCESS] Logged in %s', user.email, { userId: user.id }); + logger.info('[AUTH][SUCCESS] Logged in %s', user.email, { userId: user.id, requestId: res.locals.requestId }); res.redirect(returnTo || ENV.JETSTREAM_CLIENT_URL); }); } @@ -115,14 +115,14 @@ export async function linkCallback(req: Request, res: Response, next: NextFuncti clientUrl: new URL(ENV.JETSTREAM_CLIENT_URL!).origin, }; if (err) { - logger.warn('[AUTH][LINK][ERROR] Error with authentication %o', err); + logger.warn('[AUTH][LINK][ERROR] Error with authentication %o', err, { requestId: res.locals.requestId }); params.error = isString(err) ? err : err.message || 'Unknown Error'; params.message = (req.query.error_description as string) || undefined; return res.redirect(`/oauth-link/?${new URLSearchParams(params as any).toString()}`); } if (!userProfile) { - logger.warn('[AUTH][LINK][ERROR] no user'); - logger.warn('[AUTH][LINK][ERROR] no info %o', info); + logger.warn('[AUTH][LINK][ERROR] no user', { requestId: res.locals.requestId }); + logger.warn('[AUTH][LINK][ERROR] no info %o', info, { requestId: res.locals.requestId }); params.error = 'Authentication Error'; params.message = (req.query.error_description as string) || undefined; return res.redirect(`/oauth-link/?${new URLSearchParams(params as any).toString()}`); @@ -140,12 +140,13 @@ export async function linkCallback(req: Request, res: Response, next: NextFuncti logger.warn('[AUTH0][IDENTITY][LINK][ERROR] Failed to delete the secondary user orgs %s', userProfile.user_id, { userId: user.id, secondaryUserId: userProfile.user_id, + requestId: res.locals.requestId, }); } return res.redirect(`/oauth-link/?${new URLSearchParams(params as any).toString()}`); } catch (ex) { - logger.warn('[AUTH][LINK][ERROR] Error linking account %o', err); + logger.warn('[AUTH][LINK][ERROR] Error linking account %o', err, { requestId: res.locals.requestId }); params.error = 'Unexpected Error'; return res.redirect(`/oauth-link/?${new URLSearchParams(params as any).toString()}&clientUrl=${ENV.JETSTREAM_CLIENT_URL}`); } diff --git a/apps/api/src/app/controllers/oauth.controller.ts b/apps/api/src/app/controllers/oauth.controller.ts index 6cac681d8..765082007 100644 --- a/apps/api/src/app/controllers/oauth.controller.ts +++ b/apps/api/src/app/controllers/oauth.controller.ts @@ -1,5 +1,5 @@ import { ENV, logger } from '@jetstream/api-config'; -import { SalesforceOrgUi, SObjectOrganization, UserProfileServer } from '@jetstream/types'; +import { SObjectOrganization, SalesforceOrgUi, UserProfileServer } from '@jetstream/types'; import * as express from 'express'; import * as jsforce from 'jsforce'; import * as salesforceOrgsDb from '../db/salesforce-org.db'; @@ -51,7 +51,7 @@ export async function salesforceOauthCallback(req: express.Request, res: express returnParams.message = req.query.error_description ? (req.query.error_description as string) : 'There was an error authenticating with Salesforce.'; - logger.info('[OAUTH][ERROR] %s', req.query.error, { ...req.query }); + logger.info('[OAUTH][ERROR] %s', req.query.error, { ...req.query, requestId: res.locals.requestId }); return res.redirect(`/oauth-link/?${new URLSearchParams(returnParams as any).toString()}`); } @@ -78,7 +78,7 @@ export async function salesforceOauthCallback(req: express.Request, res: express return res.redirect(`/oauth-link/?${new URLSearchParams(returnParams as any).toString()}`); } catch (ex) { const userInfo = req.user ? { username: (req.user as any)?.displayName, userId: (req.user as any)?.user_id } : undefined; - logger.info('[OAUTH][ERROR] %o', ex.message, { userInfo }); + logger.info('[OAUTH][ERROR] %o', ex.message, { userInfo, requestId: res.locals.requestId }); returnParams.error = ex.message || 'Unexpected Error'; returnParams.message = req.query.error_description ? (req.query.error_description as string) @@ -109,7 +109,7 @@ export async function initConnectionFromOAuthResponse({ companyInfoRecord = results.records[0]; } } catch (ex) { - logger.warn(ex); + logger.warn('Error getting org info %o', ex); } const orgName = companyInfoRecord?.Name || 'Unknown Organization'; diff --git a/apps/api/src/app/controllers/orgs.controller.ts b/apps/api/src/app/controllers/orgs.controller.ts index 330dbab67..c0915ce85 100644 --- a/apps/api/src/app/controllers/orgs.controller.ts +++ b/apps/api/src/app/controllers/orgs.controller.ts @@ -58,10 +58,10 @@ export async function checkOrgHealth(req: Request, res: Response, next: NextFunc try { await conn.identity(); connectionError = null; - logger.warn('[ORG CHECK][VALID ORG]'); + logger.warn('[ORG CHECK][VALID ORG]', { requestId: res.locals.requestId }); } catch (ex) { connectionError = ERROR_MESSAGES.SFDC_EXPIRED_TOKEN; - logger.warn('[ORG CHECK][INVALID ORG] %s', ex.message); + logger.warn('[ORG CHECK][INVALID ORG] %s', ex.message, { requestId: res.locals.requestId }); } try { @@ -69,7 +69,7 @@ export async function checkOrgHealth(req: Request, res: Response, next: NextFunc await salesforceOrgsDb.updateOrg_UNSAFE(org, { connectionError }); } } catch (ex) { - logger.warn('[ERROR UPDATING INVALID ORG] %s', ex.message, { error: ex.message, userInfo }); + logger.warn('[ERROR UPDATING INVALID ORG] %s', ex.message, { error: ex.message, userInfo, requestId: res.locals.requestId }); } if (connectionError) { diff --git a/apps/api/src/app/controllers/sf-bulk-api.controller.ts b/apps/api/src/app/controllers/sf-bulk-api.controller.ts index c5c0940e9..15cd05b33 100644 --- a/apps/api/src/app/controllers/sf-bulk-api.controller.ts +++ b/apps/api/src/app/controllers/sf-bulk-api.controller.ts @@ -13,7 +13,7 @@ import { sendJson } from '../utils/response.handlers'; export const routeValidators = { createJob: [ - body('type').isIn(['INSERT', 'UPDATE', 'UPSERT', 'DELETE', 'QUERY']), + body('type').isIn(['INSERT', 'UPDATE', 'UPSERT', 'DELETE', 'QUERY', 'QUERY_ALL']), body('sObject').isString(), body('serialMode').optional().isBoolean(), body('externalIdFieldName').optional().isString(), @@ -186,7 +186,6 @@ export async function downloadResults(req: Request, res: Response, next: NextFun let isFirstChunk = true; csvParseStream.on('data', (data) => { - console.log('DATA: %o', data); data = JSON.stringify(data); if (isFirstChunk) { isFirstChunk = false; @@ -197,16 +196,21 @@ export async function downloadResults(req: Request, res: Response, next: NextFun res.write(data); }); csvParseStream.on('finish', () => { - console.log('FINISH'); res.write(']}'); - res.status(200).send(); + if (!res.headersSent) { + res.status(200).send(); + } else { + logger.warn('Response headers already sent. csvParseStream[finish]', { requestId: res.locals.requestId }); + } }); csvParseStream.on('error', (err) => { - logger.warn('Error streaming files from Salesforce. %o', err); - res.status(400).send(); + logger.warn('Error streaming files from Salesforce. %o', err, { requestId: res.locals.requestId }); + if (!res.headersSent) { + res.status(400).send(); + } else { + logger.warn('Response headers already sent. csvParseStream[error]', { requestId: res.locals.requestId }); + } }); - - // csvParseStream.pipe(res); } catch (ex) { next(new UserFacingError(ex.message)); } diff --git a/apps/api/src/app/controllers/sf-metadata-tooling.controller.ts b/apps/api/src/app/controllers/sf-metadata-tooling.controller.ts index 0a12daee4..9161b209f 100644 --- a/apps/api/src/app/controllers/sf-metadata-tooling.controller.ts +++ b/apps/api/src/app/controllers/sf-metadata-tooling.controller.ts @@ -1,14 +1,21 @@ import { ENV, logger } from '@jetstream/api-config'; import { HTTP, LOG_LEVELS, MIME_TYPES } from '@jetstream/shared/constants'; import { ensureArray, getValueOrSoapNull, sanitizeForXml, splitArrayToMaxSize, toBoolean } from '@jetstream/shared/utils'; -import { AnonymousApexResponse, AnonymousApexSoapResponse, ApexCompletionResponse, ListMetadataResult, MapOf } from '@jetstream/types'; +import { AnonymousApexResponse, ApexCompletionResponse, ListMetadataResult, MapOf } from '@jetstream/types'; import { NextFunction, Request, Response } from 'express'; import { body, param, query } from 'express-validator'; import type { DeployOptions, RetrieveRequest } from 'jsforce'; import * as jsforce from 'jsforce'; -import * as JSZip from 'jszip'; +import JSZip from 'jszip'; import { isObject, isString, toNumber } from 'lodash'; -import { buildPackageXml, getRetrieveRequestFromListMetadata, getRetrieveRequestFromManifest } from '../services/sf-misc'; +import xml2js from 'xml2js'; +import { + SalesforceRequestViaAxiosOptions, + buildPackageXml, + getRetrieveRequestFromListMetadata, + getRetrieveRequestFromManifest, + salesforceRequestViaAxios, +} from '../services/sf-misc'; import { UserFacingError } from '../utils/error-handler'; import { sendJson } from '../utils/response.handlers'; @@ -117,7 +124,7 @@ export async function deployMetadata(req: Request, res: Response, next: NextFunc const zip = new JSZip(); files.forEach((file) => zip.file(file.fullFilename, file.content)); - const results = await conn.metadata.deploy(zip.generateNodeStream(), req.body.options); + const results = await conn.metadata.deploy(zip.generateNodeStream() as any, req.body.options); sendJson(res, results); } catch (ex) { @@ -296,7 +303,7 @@ export async function checkRetrieveStatusAndRedeploy(req: Request, res: Response sendJson(res, { type: 'deploy', results: correctInvalidXmlResponseTypes(deployResults), zipFile: results.zipFile }); } else { // Deploy package as-is - const deployResults = await targetConn.metadata.deploy(oldPackage.generateNodeStream(), deployOptions); + const deployResults = await targetConn.metadata.deploy(oldPackage.generateNodeStream() as any, deployOptions); sendJson(res, { type: 'deploy', results: correctInvalidXmlResponseTypes(deployResults), zipFile: results.zipFile }); } } else { @@ -329,7 +336,8 @@ export async function anonymousApex(req: Request, res: Response, next: NextFunct let { apex, logLevel }: { apex: string; logLevel?: string } = req.body; logLevel = logLevel || 'FINEST'; const conn: jsforce.Connection = res.locals.jsforceConn; - const requestOptions: jsforce.RequestInfo = { + const requestOptions: SalesforceRequestViaAxiosOptions = { + conn, method: 'POST', url: `${conn.instanceUrl}/services/Soap/s/${conn.version}`, headers: { @@ -381,7 +389,7 @@ export async function anonymousApex(req: Request, res: Response, next: NextFunct - ${conn.accessToken} + {sessionId} @@ -392,22 +400,27 @@ export async function anonymousApex(req: Request, res: Response, next: NextFunct `, }; - const soapResponse = await conn.request(requestOptions, { responseType: 'text/xml' }); - const header = soapResponse['soapenv:Envelope']['soapenv:Header']; - const body = soapResponse?.['soapenv:Envelope']?.['soapenv:Body']?.executeAnonymousResponse?.result; - const results: AnonymousApexResponse = { - debugLog: header?.DebuggingInfo?.debugLog || '', - result: { - column: toNumber(getValueOrSoapNull(body.column) || -1), - compileProblem: getValueOrSoapNull(body.compileProblem) || null, - compiled: toBoolean(getValueOrSoapNull(body.compiled)) || false, - exceptionMessage: getValueOrSoapNull(body.exceptionMessage) || null, - exceptionStackTrace: getValueOrSoapNull(body.exceptionStackTrace) || null, - line: toNumber(getValueOrSoapNull(body.line)) || -1, - success: toBoolean(getValueOrSoapNull(body.success)) || false, - }, - }; - sendJson(res, results); + const response = await salesforceRequestViaAxios(requestOptions); + if (!response.error) { + const soapResponse = await xml2js.parseStringPromise(response.body, { explicitArray: false }); + const header = soapResponse['soapenv:Envelope']['soapenv:Header']; + const body = soapResponse['soapenv:Envelope']?.['soapenv:Body']?.executeAnonymousResponse?.result; + const results: AnonymousApexResponse = { + debugLog: header?.DebuggingInfo?.debugLog || '', + result: { + column: toNumber(getValueOrSoapNull(body.column) || -1), + compileProblem: getValueOrSoapNull(body.compileProblem) || null, + compiled: toBoolean(getValueOrSoapNull(body.compiled)) || false, + exceptionMessage: getValueOrSoapNull(body.exceptionMessage) || null, + exceptionStackTrace: getValueOrSoapNull(body.exceptionStackTrace) || null, + line: toNumber(getValueOrSoapNull(body.line)) || -1, + success: toBoolean(getValueOrSoapNull(body.success)) || false, + }, + }; + sendJson(res, results); + } else { + next(new UserFacingError(response.errorMessage)); + } } catch (ex) { next(ex); } @@ -426,7 +439,7 @@ export async function apexCompletions(req: Request, res: Response, next: NextFun }, }; - logger.info(requestOptions.url); + logger.info('Apex Completion %s', requestOptions.url, { requestId: res.locals.requestId }); const completions = await conn.request(requestOptions); sendJson(res, completions); diff --git a/apps/api/src/app/controllers/sf-misc.controller.ts b/apps/api/src/app/controllers/sf-misc.controller.ts index 1aed1a3bd..2edb7e45c 100644 --- a/apps/api/src/app/controllers/sf-misc.controller.ts +++ b/apps/api/src/app/controllers/sf-misc.controller.ts @@ -1,16 +1,13 @@ -import { ORG_VERSION_PLACEHOLDER } from '@jetstream/shared/constants'; import { toBoolean } from '@jetstream/shared/utils'; import { GenericRequestPayload, ManualRequestPayload, ManualRequestResponse } from '@jetstream/types'; -import axios, { AxiosError, AxiosRequestConfig } from 'axios'; import { NextFunction, Request, Response } from 'express'; import { body, query } from 'express-validator'; import * as jsforce from 'jsforce'; import { isObject, isString } from 'lodash'; +import * as request from 'superagent'; +import { salesforceRequestViaAxios } from '../services/sf-misc'; import { UserFacingError } from '../utils/error-handler'; import { sendJson } from '../utils/response.handlers'; -import * as request from 'superagent'; - -const SESSION_ID_RGX = /\{sessionId\}/i; export const routeValidators = { getFrontdoorLoginUrl: [], @@ -104,75 +101,18 @@ export async function makeJsforceRequest(req: Request, res: Response, next: Next export async function makeJsforceRequestViaAxios(req: Request, res: Response, next: NextFunction) { try { - const { method, headers } = req.body as ManualRequestPayload; - let { url } = req.body as ManualRequestPayload; - let { body } = req.body as ManualRequestPayload; + const { method, headers, body, url } = req.body as ManualRequestPayload; const conn: jsforce.Connection = res.locals.jsforceConn; - url = url.replace(ORG_VERSION_PLACEHOLDER, conn.version); - - const config: AxiosRequestConfig = { + const response = await salesforceRequestViaAxios({ + conn, url, method, - baseURL: conn.instanceUrl, - // X-SFDC-Session is used for some SOAP APIs, such as the bulk api - headers: { ...(headers || {}), ['Authorization']: `Bearer ${conn.accessToken}`, ['X-SFDC-Session']: conn.accessToken }, - responseType: 'text', - validateStatus: null, - timeout: 120000, - transformResponse: [], // required to avoid automatic json parsing - }; + headers, + body, + }); - if (isString(body) && SESSION_ID_RGX.test(body)) { - body = body.replace(SESSION_ID_RGX, conn.accessToken); - } - - if (method !== 'GET' && body) { - config.data = body; - } - - axios - .request(config) - .then((response) => { - sendJson(res, { - error: response.status < 200 || response.status > 300, - status: response.status, - statusText: response.statusText, - headers: JSON.stringify(response.headers || {}, null, 2), - body: response.data, - }); - }) - .catch((error: AxiosError) => { - if (error.isAxiosError) { - if (error.response) { - return sendJson(res, { - error: true, - errorMessage: null, - status: error.response.status, - statusText: error.response.statusText, - headers: JSON.stringify(error.response.headers || {}, null, 2), - body: error.response.data as any, - }); - } else if (error.request) { - return sendJson(res, { - error: true, - errorMessage: error.message || 'An unknown error has occurred.', - status: null, - statusText: null, - headers: null, - body: null, - }); - } - } - sendJson(res, { - error: true, - errorMessage: error.message || 'An unknown error has occurred, the request was not made.', - status: null, - statusText: null, - headers: null, - body: null, - }); - }); + sendJson(res, response); } catch (ex) { next(new UserFacingError(ex.message)); } diff --git a/apps/api/src/app/controllers/sf-query.controller.ts b/apps/api/src/app/controllers/sf-query.controller.ts index 297594b65..1f60b9fc8 100644 --- a/apps/api/src/app/controllers/sf-query.controller.ts +++ b/apps/api/src/app/controllers/sf-query.controller.ts @@ -1,9 +1,9 @@ import { NextFunction, Request, Response } from 'express'; +import { body, query as queryString } from 'express-validator'; import * as jsforce from 'jsforce'; import * as queryService from '../services/query'; -import { sendJson } from '../utils/response.handlers'; import { UserFacingError } from '../utils/error-handler'; -import { body, query as queryString } from 'express-validator'; +import { sendJson } from '../utils/response.handlers'; export const routeValidators = { query: [body('query').isString()], diff --git a/apps/api/src/app/controllers/socket.controller.ts b/apps/api/src/app/controllers/socket.controller.ts index 4ccfc91fe..67e61d06d 100644 --- a/apps/api/src/app/controllers/socket.controller.ts +++ b/apps/api/src/app/controllers/socket.controller.ts @@ -1,23 +1,17 @@ -import { ENV, logger } from '@jetstream/api-config'; +import { logger } from '@jetstream/api-config'; import { UserProfileServer } from '@jetstream/types'; import * as cometdClient from 'cometd-nodejs-client'; import * as express from 'express'; -import { createServer, IncomingMessage } from 'http'; +import { IncomingMessage, createServer } from 'http'; +import { nanoid } from 'nanoid'; import { Server, Socket } from 'socket.io'; import { ExtendedError } from 'socket.io/dist/namespace'; import { DefaultEventsMap } from 'socket.io/dist/typed-events'; -import { nanoid } from 'nanoid'; import { environment } from '../../environments/environment'; import * as socketUtils from '../utils/socket-utils'; -const serverUrl = ENV.JETSTREAM_SERVER_URL; cometdClient.adapt(); -/** - * FIXME: https://socket.io/docs/v4/pm2/ - * pm2 will cause issues! - */ - let io: Server; const wrapMiddleware = diff --git a/apps/api/src/app/controllers/user.controller.ts b/apps/api/src/app/controllers/user.controller.ts index 54b5257bf..4655d874e 100644 --- a/apps/api/src/app/controllers/user.controller.ts +++ b/apps/api/src/app/controllers/user.controller.ts @@ -1,15 +1,16 @@ -import { logger, mailgun } from '@jetstream/api-config'; -import { UserProfileServer } from '@jetstream/types'; +import { ENV, logger, mailgun } from '@jetstream/api-config'; +import { UserProfileAuth0Ui, UserProfileServer, UserProfileUi, UserProfileUiWithIdentities } from '@jetstream/types'; import { AxiosError } from 'axios'; import * as express from 'express'; import { body, query as queryString } from 'express-validator'; import { deleteUserAndOrgs } from '../db/transactions.db'; +import * as userDbService from '../db/user.db'; import * as auth0Service from '../services/auth0'; import { UserFacingError } from '../utils/error-handler'; import { sendJson } from '../utils/response.handlers'; export const routeValidators = { - updateProfile: [body('name').isString().isLength({ min: 1, max: 255 })], + updateProfile: [body('name').isString().isLength({ min: 1, max: 255 }), body('preferences').isObject().optional()], unlinkIdentity: [queryString('provider').isString().isLength({ min: 1 }), queryString('userId').isString().isLength({ min: 1 })], resendVerificationEmail: [queryString('provider').isString().isLength({ min: 1 }), queryString('userId').isString().isLength({ min: 1 })], deleteAccount: [body('reason').isString().optional()], @@ -45,33 +46,85 @@ export async function emailSupport(req: express.Request, res: express.Response) }), 'h:Reply-To': 'support@getjetstream.app', }); - logger.info('[SUPPORT EMAIL][EMAIL SENT] %s', results.id); + logger.info('[SUPPORT EMAIL][EMAIL SENT] %s', results.id, { requestId: res.locals.requestId }); sendJson(res); } catch (ex) { - logger.error('[SUPPORT EMAIL][ERROR] %s', ex.message || 'An unknown error has occurred.', { userId: user.id }); - logger.error(ex.stack); + logger.error('[SUPPORT EMAIL][ERROR] %s', ex.message || 'An unknown error has occurred.', { + userId: user.id, + requestId: res.locals.requestId, + }); + logger.error('%o', ex.stack, { requestId: res.locals.requestId }); throw new UserFacingError('There was a problem sending the email'); } } export async function getUserProfile(req: express.Request, res: express.Response) { - const user = req.user as UserProfileServer; - sendJson(res, user._json); + const auth0User = req.user as UserProfileServer; + + // use fallback locally and on CI + if (ENV.EXAMPLE_USER_OVERRIDE && ENV.EXAMPLE_USER_PROFILE && req.hostname === 'localhost') { + sendJson(res, ENV.EXAMPLE_USER_PROFILE); + return; + } + + const user = await userDbService.findByUserId(auth0User.id); + if (!user) { + throw new UserFacingError('User not found'); + } + const userProfileUi: UserProfileUi = { + ...(auth0User._json as any), + id: user.id, + userId: user.userId, + createdAt: user.createdAt.toISOString(), + updatedAt: user.updatedAt.toISOString(), + preferences: { + skipFrontdoorLogin: user.preferences?.skipFrontdoorLogin, + }, + }; + sendJson(res, userProfileUi); +} + +async function getFullUserProfileFn(sessionUser: UserProfileServer, auth0User?: UserProfileAuth0Ui) { + auth0User = auth0User || (await auth0Service.getUser(sessionUser)); + const jetstreamUser = await userDbService.findByUserId(sessionUser.id); + if (!jetstreamUser) { + throw new UserFacingError('User not found'); + } + const response: UserProfileUiWithIdentities = { + id: jetstreamUser.id, + userId: sessionUser.id, + name: jetstreamUser.name || '', + email: jetstreamUser.email, + emailVerified: auth0User.email_verified, + username: auth0User.username || '', + nickname: auth0User.nickname, + picture: auth0User.picture, + preferences: { + skipFrontdoorLogin: jetstreamUser.preferences?.skipFrontdoorLogin ?? false, + }, + identities: auth0User.identities, + createdAt: jetstreamUser.createdAt.toISOString(), + updatedAt: jetstreamUser.updatedAt.toISOString(), + }; + return response; } /** Get profile from Auth0 */ export async function getFullUserProfile(req: express.Request, res: express.Response) { const user = req.user as UserProfileServer; try { - const auth0User = await auth0Service.getUser(user); - sendJson(res, auth0User); + const response = await getFullUserProfileFn(user); + sendJson(res, response); } catch (ex) { if (ex.isAxiosError) { const error: AxiosError = ex; if (error.response) { - logger.error('[AUTH0][PROFILE FETCH][ERROR] %o', error.response.data, { userId: user.id }); + logger.error('[AUTH0][PROFILE FETCH][ERROR] %o', error.response.data, { userId: user.id, requestId: res.locals.requestId }); } else if (error.request) { - logger.error('[AUTH0][PROFILE FETCH][ERROR] %s', error.message || 'An unknown error has occurred.', { userId: user.id }); + logger.error('[AUTH0][PROFILE FETCH][ERROR] %s', error.message || 'An unknown error has occurred.', { + userId: user.id, + requestId: res.locals.requestId, + }); } } throw new UserFacingError('There was an error obtaining your profile information'); @@ -80,18 +133,24 @@ export async function getFullUserProfile(req: express.Request, res: express.Resp export async function updateProfile(req: express.Request, res: express.Response) { const user = req.user as UserProfileServer; - const userProfile = { name: req.body.name }; + const userProfile = req.body as UserProfileUiWithIdentities; try { + // check for name change, if so call auth0 to update const auth0User = await auth0Service.updateUser(user, userProfile); - sendJson(res, auth0User); + // update name and preferences locally + const response = await getFullUserProfileFn(user, auth0User); + sendJson(res, response); } catch (ex) { if (ex.isAxiosError) { const error: AxiosError = ex; if (error.response) { - logger.error('[AUTH0][PROFILE][ERROR] %o', error.response.data, { userId: user.id }); + logger.error('[AUTH0][PROFILE][ERROR] %o', error.response.data, { userId: user.id, requestId: res.locals.requestId }); } else if (error.request) { - logger.error('[AUTH0][PROFILE][ERROR] %s', error.message || 'An unknown error has occurred.', { userId: user.id }); + logger.error('[AUTH0][PROFILE][ERROR] %s', error.message || 'An unknown error has occurred.', { + userId: user.id, + requestId: res.locals.requestId, + }); } } throw new UserFacingError('There was an error updating the user profile'); @@ -105,14 +164,18 @@ export async function unlinkIdentity(req: express.Request, res: express.Response const userId = req.query.userId as string; const auth0User = await auth0Service.unlinkIdentity(user, { provider, userId }); - sendJson(res, auth0User); + const response = await getFullUserProfileFn(user, auth0User); + sendJson(res, response); } catch (ex) { if (ex.isAxiosError) { const error: AxiosError = ex; if (error.response) { - logger.error('[AUTH0][UNLINK][ERROR] %o', error.response.data, { userId: user.id }); + logger.error('[AUTH0][UNLINK][ERROR] %o', error.response.data, { userId: user.id, requestId: res.locals.requestId }); } else if (error.request) { - logger.error('[AUTH0][UNLINK][ERROR] %s', error.message || 'An unknown error has occurred.', { userId: user.id }); + logger.error('[AUTH0][UNLINK][ERROR] %s', error.message || 'An unknown error has occurred.', { + userId: user.id, + requestId: res.locals.requestId, + }); } } throw new UserFacingError('There was an error unlinking the account'); @@ -124,15 +187,18 @@ export async function resendVerificationEmail(req: express.Request, res: express const provider = req.query.provider as string; const userId = req.query.userId as string; try { - const auth0User = await auth0Service.resendVerificationEmail(user, { provider, userId }); - sendJson(res, auth0User); + await auth0Service.resendVerificationEmail(user, { provider, userId }); + sendJson(res); } catch (ex) { if (ex.isAxiosError) { const error: AxiosError = ex; if (error.response) { - logger.error('[AUTH0][EMAIL VERIFICATION][ERROR] %o', error.response.data, { userId: user.id }); + logger.error('[AUTH0][EMAIL VERIFICATION][ERROR] %o', error.response.data, { userId: user.id, requestId: res.locals.requestId }); } else if (error.request) { - logger.error('[AUTH0][EMAIL VERIFICATION][ERROR] %s', error.message || 'An unknown error has occurred.', { userId: user.id }); + logger.error('[AUTH0][EMAIL VERIFICATION][ERROR] %s', error.message || 'An unknown error has occurred.', { + userId: user.id, + requestId: res.locals.requestId, + }); } } throw new UserFacingError('There was an error re-sending the verification email'); @@ -177,19 +243,19 @@ export async function deleteAccount(req: express.Request, res: express.Response) 'h:Reply-To': 'support@getjetstream.app', }) .then((results) => { - logger.info('[ACCOUNT DELETE][EMAIL SENT] %s', results.id); + logger.info('[ACCOUNT DELETE][EMAIL SENT] %s', results.id, { requestId: res.locals.requestId }); }) .catch((error) => { - logger.error('[ACCOUNT DELETE][ERROR SENDING EMAIL SUMMARY] %s', error.message); + logger.error('[ACCOUNT DELETE][ERROR SENDING EMAIL SUMMARY] %s', error.message, { requestId: res.locals.requestId }); }); } catch (ex) { - logger.error('[ACCOUNT DELETE][ERROR SENDING EMAIL SUMMARY] %s', ex.message); + logger.error('[ACCOUNT DELETE][ERROR SENDING EMAIL SUMMARY] %s', ex.message, { requestId: res.locals.requestId }); } // Destroy session - don't wait for response req.session.destroy((error) => { if (error) { - logger.error('[ACCOUNT DELETE][ERROR DESTROYING SESSION] %s', error.message); + logger.error('[ACCOUNT DELETE][ERROR DESTROYING SESSION] %s', error.message, { requestId: res.locals.requestId }); } }); @@ -198,9 +264,12 @@ export async function deleteAccount(req: express.Request, res: express.Response) if (ex.isAxiosError) { const error: AxiosError = ex; if (error.response) { - logger.error('[ACCOUNT DELETE][FATAL ERROR] %o', error.response.data, { userId: user.id }); + logger.error('[ACCOUNT DELETE][FATAL ERROR] %o', error.response.data, { userId: user.id, requestId: res.locals.requestId }); } else if (error.request) { - logger.error('[ACCOUNT DELETE][FATAL ERROR] %s', error.message || 'An unknown error has occurred.', { userId: user.id }); + logger.error('[ACCOUNT DELETE][FATAL ERROR] %s', error.message || 'An unknown error has occurred.', { + userId: user.id, + requestId: res.locals.requestId, + }); } } throw new UserFacingError('There was a problem deleting your account, contact support@getjetstream.app for assistance.'); diff --git a/apps/api/src/app/db/user.db.ts b/apps/api/src/app/db/user.db.ts index 2704ff09e..6200e2b3c 100644 --- a/apps/api/src/app/db/user.db.ts +++ b/apps/api/src/app/db/user.db.ts @@ -1,21 +1,57 @@ import { ENV, logger, prisma } from '@jetstream/api-config'; import { UserProfileServer } from '@jetstream/types'; -import { User } from '@prisma/client'; +import { Prisma, User } from '@prisma/client'; + +const userSelect: Prisma.UserSelect = { + appMetadata: true, + createdAt: true, + email: true, + id: true, + name: true, + nickname: true, + picture: true, + preferences: { + select: { + skipFrontdoorLogin: true, + }, + }, + updatedAt: true, + userId: true, +}; /** * Find by Auth0 userId, not Jetstream Id */ -async function findByUserId(userId: string) { - return await prisma.user.findUnique({ - where: { userId: userId }, +export async function findByUserId(userId: string) { + const user = await prisma.user.findUnique({ + where: { userId }, + select: userSelect, }); + return user; } -export async function updateUser(user: UserProfileServer, data: { name: string }): Promise { +export async function updateUser( + user: UserProfileServer, + data: { name: string; preferences: { skipFrontdoorLogin: boolean } } +): Promise { try { + const existingUser = await prisma.user.findUnique({ + where: { userId: user.id }, + select: { id: true, preferences: { select: { skipFrontdoorLogin: true } } }, + }); + const skipFrontdoorLogin = data.preferences.skipFrontdoorLogin ?? (existingUser?.preferences?.skipFrontdoorLogin || false); const updatedUser = await prisma.user.update({ where: { userId: user.id }, - data: { name: data.name }, + data: { + name: data.name, + preferences: { + upsert: { + create: { skipFrontdoorLogin }, + update: { skipFrontdoorLogin }, + }, + }, + }, + select: userSelect, }); return updatedUser; } catch (ex) { @@ -36,7 +72,14 @@ export async function createOrUpdateUser(user: UserProfileServer): Promise<{ cre where: { userId: user.id }, data: { appMetadata: JSON.stringify(user._json[ENV.AUTH_AUDIENCE!]), + preferences: { + upsert: { + create: { skipFrontdoorLogin: false }, + update: { skipFrontdoorLogin: false }, + }, + }, }, + select: userSelect, }); logger.debug('[DB][USER][UPDATED] %s', user.id, { userId: user.id, id: existingUser.id }); return { created: false, user: updatedUser }; @@ -49,7 +92,9 @@ export async function createOrUpdateUser(user: UserProfileServer): Promise<{ cre nickname: user._json.nickname, picture: user._json.picture, appMetadata: JSON.stringify(user._json[ENV.AUTH_AUDIENCE!]), + preferences: { create: { skipFrontdoorLogin: false } }, }, + select: userSelect, }); logger.debug('[DB][USER][CREATED] %s', user.id, { userId: user.id, id: createdUser.id }); return { created: true, user: createdUser }; diff --git a/apps/api/src/app/routes/api.routes.ts b/apps/api/src/app/routes/api.routes.ts index 95d8413f8..ef5c817a7 100644 --- a/apps/api/src/app/routes/api.routes.ts +++ b/apps/api/src/app/routes/api.routes.ts @@ -1,7 +1,7 @@ import { ENV } from '@jetstream/api-config'; -import * as express from 'express'; +import express from 'express'; import Router from 'express-promise-router'; -import * as multer from 'multer'; +import multer from 'multer'; import * as imageController from '../controllers/image.controller'; import * as orgsController from '../controllers/orgs.controller'; import * as salesforceApiReqController from '../controllers/salesforce-api-requests.controller'; diff --git a/apps/api/src/app/routes/route.middleware.ts b/apps/api/src/app/routes/route.middleware.ts index 841753e5f..cc9573fe0 100644 --- a/apps/api/src/app/routes/route.middleware.ts +++ b/apps/api/src/app/routes/route.middleware.ts @@ -8,11 +8,17 @@ import * as express from 'express'; import { ValidationChain, validationResult } from 'express-validator'; import * as jsforce from 'jsforce'; import { isNumber } from 'lodash'; +import { v4 as uuid } from 'uuid'; import * as salesforceOrgsDb from '../db/salesforce-org.db'; import { updateUserLastActivity } from '../services/auth0'; import { getJsforceOauth2 } from '../utils/auth-utils'; import { AuthenticationError, NotFoundError, UserFacingError } from '../utils/error-handler'; +export function addContextMiddleware(req: express.Request, res: express.Response, next: express.NextFunction) { + res.locals.requestId = uuid(); + next(); +} + /** * Set's cookie that is used by front-end application * @param req @@ -34,11 +40,11 @@ export function setApplicationCookieMiddleware(req: express.Request, res: expres export function logRoute(req: express.Request, res: express.Response, next: express.NextFunction) { res.locals.path = req.path; - // logger.info(req.method, req.originalUrl); const userInfo = req.user ? { username: (req.user as any)?.displayName, userId: (req.user as any)?.user_id } : undefined; logger.debug('[REQ] %s %s', req.method, req.originalUrl, { method: req.method, url: req.originalUrl, + requestId: res.locals.requestId, agent: req.header('User-Agent'), ip: req.headers[HTTP.HEADERS.CF_Connecting_IP] || req.headers[HTTP.HEADERS.X_FORWARDED_FOR] || req.connection.remoteAddress, country: req.headers[HTTP.HEADERS.CF_IPCountry], @@ -77,6 +83,7 @@ export function blockBotByUserAgentMiddleware(req: express.Request, res: express blocked: true, method: req.method, url: req.originalUrl, + requestId: res.locals.requestId, agent: req.header('User-Agent'), referrer: req.get('Referrer'), ip: req.headers[HTTP.HEADERS.CF_Connecting_IP] || req.headers[HTTP.HEADERS.X_FORWARDED_FOR] || req.connection.remoteAddress, @@ -106,23 +113,37 @@ export async function checkAuth(req: express.Request, res: express.Response, nex // Update auth0 with expiration date updateUserLastActivity(req.user as UserProfileServer, fromUnixTime(req.session.activityExp)) .then(() => { - logger.debug('[AUTH][LAST-ACTIVITY][UPDATED] %s', req.session.activityExp, { userId: (req.user as any)?.user_id }); + logger.debug('[AUTH][LAST-ACTIVITY][UPDATED] %s', req.session.activityExp, { + userId: (req.user as any)?.user_id, + requestId: res.locals.requestId, + }); }) .catch((err) => { // send error to rollbar const error: AxiosError = err; if (error.response) { - logger.error('[AUTH][LAST-ACTIVITY][ERROR] %o', error.response.data, { userId: (req.user as any)?.user_id }); + logger.error('[AUTH][LAST-ACTIVITY][ERROR] %o', error.response.data, { + userId: (req.user as any)?.user_id, + requestId: res.locals.requestId, + }); } else if (error.request) { logger.error('[AUTH][LAST-ACTIVITY][ERROR] %s', error.message || 'An unknown error has occurred.', { userId: (req.user as any)?.user_id, + requestId: res.locals.requestId, }); } - rollbarServer.error('Error updating Auth0 activityExp', { message: err.message, stack: err.stack }); + rollbarServer.error('Error updating Auth0 activityExp', { + message: err.message, + stack: err.stack, + requestId: res.locals.requestId, + }); }); } } catch (ex) { - logger.warn('[AUTH][LAST-ACTIVITY][ERROR] Exception: %s', ex.message, { userId: (req.user as any)?.user_id }); + logger.warn('[AUTH][LAST-ACTIVITY][ERROR] Exception: %s', ex.message, { + userId: (req.user as any)?.user_id, + requestId: res.locals.requestId, + }); } return next(); } @@ -130,6 +151,7 @@ export async function checkAuth(req: express.Request, res: express.Response, nex blocked: true, method: req.method, url: req.originalUrl, + requestId: res.locals.requestId, agent: req.header('User-Agent'), ip: req.headers[HTTP.HEADERS.CF_Connecting_IP] || req.headers[HTTP.HEADERS.X_FORWARDED_FOR] || req.connection.remoteAddress, country: req.headers[HTTP.HEADERS.CF_IPCountry], @@ -141,7 +163,7 @@ export async function addOrgsToLocal(req: express.Request, res: express.Response try { if (req.get(HTTP.HEADERS.X_SFDC_ID) || req.query[HTTP.HEADERS.X_SFDC_ID]) { res.locals = res.locals || {}; - const results = await getOrgFromHeaderOrQuery(req, HTTP.HEADERS.X_SFDC_ID, HTTP.HEADERS.X_SFDC_API_VERSION); + const results = await getOrgFromHeaderOrQuery(req, HTTP.HEADERS.X_SFDC_ID, HTTP.HEADERS.X_SFDC_API_VERSION, res.locals.requestId); if (results) { const { org, connection } = results; res.locals.org = org; @@ -150,7 +172,12 @@ export async function addOrgsToLocal(req: express.Request, res: express.Response } if (req.get(HTTP.HEADERS.X_SFDC_ID_TARGET) || req.query[HTTP.HEADERS.X_SFDC_ID_TARGET]) { res.locals = res.locals || {}; - const results = await getOrgFromHeaderOrQuery(req, HTTP.HEADERS.X_SFDC_ID_TARGET, HTTP.HEADERS.X_SFDC_API_TARGET_VERSION); + const results = await getOrgFromHeaderOrQuery( + req, + HTTP.HEADERS.X_SFDC_ID_TARGET, + HTTP.HEADERS.X_SFDC_API_TARGET_VERSION, + res.locals.requestId + ); if (results) { if (results) { const { org, connection } = results; @@ -160,7 +187,7 @@ export async function addOrgsToLocal(req: express.Request, res: express.Response } } } catch (ex) { - logger.warn('[INIT-ORG][ERROR] %o', ex); + logger.warn('[INIT-ORG][ERROR] %o', ex, { requestId: res.locals.requestId }); return next(new UserFacingError('There was an error initializing the connection to Salesforce')); } @@ -173,18 +200,18 @@ export async function addOrgsToLocal(req: express.Request, res: express.Response export async function monkeyPatchOrgsToRequest(req: express.Request, res: express.Response, next: express.NextFunction) { try { if (req.get(HTTP.HEADERS.X_SFDC_ID) || req.query[HTTP.HEADERS.X_SFDC_ID]) { - const results = await getOrgFromHeaderOrQuery(req, HTTP.HEADERS.X_SFDC_ID, HTTP.HEADERS.X_SFDC_API_VERSION); + const results = await getOrgFromHeaderOrQuery(req, HTTP.HEADERS.X_SFDC_ID, HTTP.HEADERS.X_SFDC_API_VERSION, res.locals.requestId); if (results) { const { org, connection } = results; res.locals = { org, jsforceConn: connection }; (req as any).locals = res.locals; } else { - logger.info('[INIT-ORG][ERROR] An org did not exist on locals - Monkey Patch'); + logger.info('[INIT-ORG][ERROR] An org did not exist on locals - Monkey Patch', { requestId: res.locals.requestId }); return next(new UserFacingError('An org is required for this action')); } } } catch (ex) { - logger.warn('[INIT-ORG][ERROR] %o', ex); + logger.warn('[INIT-ORG][ERROR] %o', ex, { requestId: res.locals.requestId }); return next(new UserFacingError('There was an error initializing the connection to Salesforce')); } @@ -193,7 +220,7 @@ export async function monkeyPatchOrgsToRequest(req: express.Request, res: expres export function ensureOrgExists(req: express.Request, res: express.Response, next: express.NextFunction) { if (!res.locals?.jsforceConn) { - logger.info('[INIT-ORG][ERROR] An org did not exist on locals'); + logger.info('[INIT-ORG][ERROR] An org did not exist on locals', { requestId: res.locals.requestId }); return next(new UserFacingError('An org is required for this action')); } next(); @@ -201,7 +228,7 @@ export function ensureOrgExists(req: express.Request, res: express.Response, nex export function ensureTargetOrgExists(req: express.Request, res: express.Response, next: express.NextFunction) { if (!res.locals?.targetJsforceConn) { - logger.info('[INIT-ORG][ERROR] A target org did not exist on locals'); + logger.info('[INIT-ORG][ERROR] A target org did not exist on locals', { requestId: res.locals.requestId }); return next(new UserFacingError('A target org is required for this action')); } next(); @@ -217,7 +244,7 @@ export function ensureTargetOrgExists(req: express.Request, res: express.Respons * @param headerKey * @param versionHeaderKey */ -export async function getOrgFromHeaderOrQuery(req: express.Request, headerKey: string, versionHeaderKey: string) { +export async function getOrgFromHeaderOrQuery(req: express.Request, headerKey: string, versionHeaderKey: string, requestId?: string) { const uniqueId = (req.get(headerKey) || req.query[headerKey]) as string; // TODO: not yet implemented on the front-end const apiVersion = (req.get(versionHeaderKey) || req.query[versionHeaderKey]) as string | undefined; @@ -230,10 +257,16 @@ export async function getOrgFromHeaderOrQuery(req: express.Request, headerKey: s return; } - return getOrgForRequest(user, uniqueId, apiVersion, includeCallOptions); + return getOrgForRequest(user, uniqueId, apiVersion, includeCallOptions, requestId); } -export async function getOrgForRequest(user: UserProfileServer, uniqueId: string, apiVersion?: string, includeCallOptions?: boolean) { +export async function getOrgForRequest( + user: UserProfileServer, + uniqueId: string, + apiVersion?: string, + includeCallOptions?: boolean, + requestId?: string +) { const org = await salesforceOrgsDb.findByUniqueId_UNSAFE(user.id, uniqueId); if (!org) { throw new UserFacingError('An org was not found with the provided id'); @@ -254,6 +287,7 @@ export async function getOrgForRequest(user: UserProfileServer, uniqueId: string // http://www.fishofprey.com/2016/03/salesforce-forcecom-ide-superpowers.html // FIXME: this breaks some orgs // client: `apex_eclipse/v${apiVersion || org.apiVersion || ENV.SFDC_API_VERSION}`, + client: 'jetstream', }, }; @@ -272,9 +306,9 @@ export async function getOrgForRequest(user: UserProfileServer, uniqueId: string return; } await salesforceOrgsDb.updateAccessToken_UNSAFE(org, accessToken, conn.refreshToken); - logger.info('[ORG][REFRESH] Org refreshed successfully'); + logger.info('[ORG][REFRESH] Org refreshed successfully', { requestId }); } catch (ex) { - logger.error('[ORG][REFRESH] Error saving refresh token', ex); + logger.error('[ORG][REFRESH] Error saving refresh token', ex, { requestId }); } }; diff --git a/apps/api/src/app/services/auth0.ts b/apps/api/src/app/services/auth0.ts index 347474891..ec13e9861 100644 --- a/apps/api/src/app/services/auth0.ts +++ b/apps/api/src/app/services/auth0.ts @@ -1,5 +1,5 @@ import { ENV, logger } from '@jetstream/api-config'; -import { UserProfileAuth0Identity, UserProfileAuth0Ui, UserProfileServer } from '@jetstream/types'; +import { UserProfileAuth0Identity, UserProfileAuth0Ui, UserProfileServer, UserProfileUiWithIdentities } from '@jetstream/types'; import axios, { AxiosError } from 'axios'; import { addHours, addSeconds, formatISO, isBefore } from 'date-fns'; import * as userDb from '../db/user.db'; @@ -76,10 +76,13 @@ export async function updateUserLastActivity(user: UserProfileServer, lastActivi ).data; } -export async function updateUser(user: UserProfileServer, userProfile: { name: string }): Promise { +export async function updateUser(user: UserProfileServer, userProfile: UserProfileUiWithIdentities): Promise { await initAuthorizationToken(user); - // update on Auth0 - await axiosAuth0.patch(`/api/v2/users/${user.id}`, userProfile); + + if (user.displayName !== userProfile.name) { + // update on Auth0 if name changed (not allowed for OAuth connections) + await axiosAuth0.patch(`/api/v2/users/${user.id}`, { name: userProfile.name }); + } // update locally await userDb.updateUser(user, userProfile); // re-fetch user from Auth0 diff --git a/apps/api/src/app/services/query.ts b/apps/api/src/app/services/query.ts index e256d58b9..00585c1ea 100644 --- a/apps/api/src/app/services/query.ts +++ b/apps/api/src/app/services/query.ts @@ -2,7 +2,7 @@ import { logger } from '@jetstream/api-config'; import { QueryResults, QueryResultsColumn, QueryResultsColumns } from '@jetstream/api-interfaces'; import type { Connection } from 'jsforce'; -import { parseQuery, Query } from 'soql-parser-js'; +import { Query, parseQuery } from 'soql-parser-js'; import { QueryColumnMetadata, QueryColumnsSfdc } from '../types/types'; /** @@ -40,7 +40,7 @@ export async function queryRecords( groupBy: tempColumns.groupBy, idSelected: tempColumns.idSelected, keyPrefix: tempColumns.keyPrefix, - columns: tempColumns.columnMetadata.flatMap((column) => flattenQueryColumn(column)), + columns: tempColumns.columnMetadata?.flatMap((column) => flattenQueryColumn(column)), }; } catch (ex) { logger.error('Error fetching columns', ex); @@ -76,7 +76,7 @@ function flattenQueryColumn(column: QueryColumnMetadata, prevColumnPath?: string if (Array.isArray(column.joinColumns) && column.joinColumns.length > 0) { if (column.foreignKeyName) { // Parent Query - output = output.concat(column.joinColumns.flatMap((joinColumn) => flattenQueryColumn(joinColumn, currColumnPath))); + output = output.concat((column.joinColumns || [])?.flatMap((joinColumn) => flattenQueryColumn(joinColumn, currColumnPath))); } else { // Child query output.push({ @@ -92,7 +92,7 @@ function flattenQueryColumn(column: QueryColumnMetadata, prevColumnPath?: string numberType: column.numberType, textType: column.textType, updatable: column.updatable, - childColumnPaths: column.joinColumns.flatMap((joinColumn) => flattenQueryColumn(joinColumn, currColumnPath)), + childColumnPaths: (column.joinColumns || [])?.flatMap((joinColumn) => flattenQueryColumn(joinColumn, currColumnPath)), }); } } else { diff --git a/apps/api/src/app/services/sf-misc.ts b/apps/api/src/app/services/sf-misc.ts index 2717fb3fe..595212ae6 100644 --- a/apps/api/src/app/services/sf-misc.ts +++ b/apps/api/src/app/services/sf-misc.ts @@ -1,12 +1,165 @@ -import { ensureArray, orderObjectsBy } from '@jetstream/shared/utils'; -import { ListMetadataResult, MapOf } from '@jetstream/types'; +import { ENV } from '@jetstream/api-config'; +import { HTTP, ORG_VERSION_PLACEHOLDER } from '@jetstream/shared/constants'; +import { ensureArray, getFullNameFromListMetadata, orderObjectsBy } from '@jetstream/shared/utils'; +import { ListMetadataResult, ManualRequestPayload, ManualRequestResponse, MapOf } from '@jetstream/types'; +import axios, { AxiosError, AxiosRequestConfig } from 'axios'; import type { PackageTypeMembers, RetrieveRequest } from 'jsforce'; -import { get as lodashGet, isObjectLike, isString } from 'lodash'; +import * as jsforce from 'jsforce'; +import { isObjectLike, isString, get as lodashGet } from 'lodash'; import { create as xmlBuilder } from 'xmlbuilder2'; import { UserFacingError } from '../utils/error-handler'; +const SESSION_ID_RGX = /\{sessionId\}/i; const VALID_PACKAGE_VERSION = /^[0-9]+\.[0-9]+$/; +export interface SalesforceRequestViaAxiosOptions extends ManualRequestPayload { + conn: jsforce.Connection; + /** + * If true, the function will throw an error if the request fails + * @default false + */ + throwIfError?: boolean; + /** + * If true, the function will attempt to refresh the token and retry the request + * @default true + */ + retryOnAuthFailure?: boolean; +} + +/** + * Make API call to Salesforce without using JSForce + */ +export async function salesforceRequestViaAxios(options: SalesforceRequestViaAxiosOptions): Promise { + const { conn, method, headers = {}, throwIfError = false, retryOnAuthFailure = true } = options; + let { body, url } = options; + try { + url = url.replace(ORG_VERSION_PLACEHOLDER, conn.version); + + const config: AxiosRequestConfig = { + url, + method, + baseURL: conn.instanceUrl, + // X-SFDC-Session is used for some SOAP APIs, such as the bulk api + headers: { + [HTTP.HEADERS.CONTENT_TYPE]: HTTP.CONTENT_TYPE.JSON, + [HTTP.HEADERS.ACCEPT]: HTTP.CONTENT_TYPE.JSON, + ...headers, + ['Authorization']: `Bearer ${conn.accessToken}`, + ['X-SFDC-Session']: conn.accessToken, + }, + responseType: 'text', + // validateStatus: false, + timeout: 120000, + transformResponse: [], // required to avoid automatic json parsing + }; + + if (isString(body) && SESSION_ID_RGX.test(body)) { + body = body.replace(SESSION_ID_RGX, conn.accessToken); + } + + if (method !== 'GET' && body) { + config.data = body; + } + + const response = await axios.request(config); + + return { + error: false, + status: response.status, + statusText: response.statusText, + headers: JSON.stringify(response.headers || {}, null, 2), + body: response.data, + }; + } catch (ex) { + if (retryOnAuthFailure && ex instanceof AxiosError) { + const response = ex.response; + if (response.status === 401 || (isString(response.data) && response.data.includes('INVALID_SESSION_ID'))) { + // attempt another API call which should auto-refresh and try again + try { + await refreshAccessToken(conn); + } catch (ex) { + console.error('Failed to refresh token', ex); + } + return await salesforceRequestViaAxios({ ...options, retryOnAuthFailure: false }); + } + } + if (throwIfError) { + throw ex; + } + if (ex instanceof AxiosError) { + const response = ex.response; + if (response) { + return { + error: response.status < 200 || response.status > 300, + status: response.status, + statusText: response.statusText, + headers: JSON.stringify(response.headers || {}, null, 2), + body: response.data, + }; + } else if (ex.request) { + return { + error: true, + errorMessage: ex.message || 'An unknown error has occurred.', + status: null, + statusText: null, + headers: null, + body: null, + }; + } + } else if (ex instanceof Error) { + return { + error: true, + errorMessage: ex.message || 'An unknown error has occurred.', + status: null, + statusText: null, + headers: null, + body: null, + }; + } + return { + error: true, + errorMessage: ex?.message || 'An unknown error has occurred, the request was not made.', + status: null, + statusText: null, + headers: null, + body: null, + }; + } +} + +export async function refreshAccessToken(conn: jsforce.Connection): Promise { + try { + const response = await axios.request<{ access_token: string }>({ + url: '/services/oauth2/token', + method: 'POST', + baseURL: conn.instanceUrl, + // X-SFDC-Session is used for some SOAP APIs, such as the bulk api + headers: { + [HTTP.HEADERS.CONTENT_TYPE]: HTTP.CONTENT_TYPE.FORM_URL, + [HTTP.HEADERS.ACCEPT]: HTTP.CONTENT_TYPE.JSON, + }, + responseType: 'json', + timeout: 20000, + data: new URLSearchParams({ + grant_type: 'refresh_token', + client_id: ENV.SFDC_CONSUMER_KEY, + client_secret: ENV.SFDC_CONSUMER_SECRET, + refresh_token: conn.refreshToken, + }).toString(), + }); + + conn.accessToken = response.data.access_token; + try { + conn.emit('refresh', conn.accessToken, conn.refreshToken); + } catch (ex) { + console.error('Failed to emit refresh event', ex); + } + } catch (ex) { + console.error('Failed to refresh token', ex); + throw ex; + } +} + export function buildPackageXml(types: MapOf, version: string, otherFields: MapOf = {}, prettyPrint = true) { // prettier-ignore const packageNode = xmlBuilder({ version: '1.0', encoding: 'UTF-8' }) @@ -15,8 +168,14 @@ export function buildPackageXml(types: MapOf, version: str Object.keys(types).forEach((metadataType) => { const typesNode = packageNode.ele('types'); if (types[metadataType].length) { - orderObjectsBy(types[metadataType], 'fullName').forEach(({ fullName }) => { - typesNode.ele('members').txt(fullName); + orderObjectsBy(types[metadataType], 'fullName').forEach(({ fullName, namespacePrefix }) => { + typesNode.ele('members').txt( + getFullNameFromListMetadata({ + fullName, + metadataType, + namespace: namespacePrefix, + }) + ); }); typesNode.ele('name').txt(metadataType); } @@ -42,7 +201,13 @@ export function getRetrieveRequestFromListMetadata(types: MapOf { const members = types[metadataName]; return { - members: members.map(({ fullName }) => fullName), + members: members.map(({ fullName, namespacePrefix }) => { + return getFullNameFromListMetadata({ + fullName, + metadataType: metadataName, + namespace: namespacePrefix, + }); + }), name: metadataName, }; }), diff --git a/apps/api/src/app/utils/error-handler.ts b/apps/api/src/app/utils/error-handler.ts index 193310e6b..a135ff033 100644 --- a/apps/api/src/app/utils/error-handler.ts +++ b/apps/api/src/app/utils/error-handler.ts @@ -6,7 +6,7 @@ export class UserFacingError extends Error { constructor(message: string | Error, additionalData?: any) { if (message instanceof Error) { if (message.message.startsWith('(res: express.Response, content?: ResponseType, status = 200) { + if (res.headersSent) { + logger.warn('Response headers already sent', { requestId: res.locals.requestId }); + try { + rollbarServer.warn('Response not handled by sendJson, headers already sent', new Error('headers already sent'), { + requestId: res.locals.requestId, + }); + } catch (ex) { + logger.error('Error sending to Rollbar', ex, { requestId: res.locals.requestId }); + } + return; + } res.status(status); return res.json({ data: content || {} }); } @@ -19,6 +43,7 @@ export function blockBotHandler(req: express.Request, res: express.Response) { blocked: true, method: req.method, url: req.originalUrl, + requestId: res.locals.requestId, agent: req.header('User-Agent'), referrer: req.get('Referrer'), ip: req.headers[HTTP.HEADERS.CF_Connecting_IP] || req.headers[HTTP.HEADERS.X_FORWARDED_FOR] || req.connection.remoteAddress, @@ -37,12 +62,23 @@ export async function uncaughtErrorHandler(err: any, req: express.Request, res: error: err.message || err, method: req.method, url: req.originalUrl, + requestId: res.locals.requestId, agent: req.header('User-Agent'), ip: req.headers[HTTP.HEADERS.CF_Connecting_IP] || req.headers[HTTP.HEADERS.X_FORWARDED_FOR] || req.connection.remoteAddress, country: req.headers[HTTP.HEADERS.CF_IPCountry], ...userInfo, }); + if (res.headersSent) { + logger.warn('Response headers already sent', { requestId: res.locals.requestId }); + try { + rollbarServer.warn('Error not handled by error handler, headers already sent', req, userInfo, err, new Error('headers already sent')); + } catch (ex) { + logger.error('Error sending to Rollbar', ex, { requestId: res.locals.requestId }); + } + return; + } + const isJson = (req.get(HTTP.HEADERS.ACCEPT) || '').includes(HTTP.CONTENT_TYPE.JSON); // If org had a connection error, ensure that the database is updated @@ -58,7 +94,11 @@ export async function uncaughtErrorHandler(err: any, req: express.Request, res: const org = res.locals.org as SalesforceOrg; await salesforceOrgsDb.updateOrg_UNSAFE(org, { connectionError: ERROR_MESSAGES.SFDC_EXPIRED_TOKEN }); } catch (ex) { - logger.warn('[RESPONSE][ERROR UPDATING INVALID ORG] %s', ex.message, { error: ex.message, userInfo }); + logger.warn('[RESPONSE][ERROR UPDATING INVALID ORG] %s', ex.message, { + error: ex.message, + userInfo, + requestId: res.locals.requestId, + }); } } @@ -104,13 +144,13 @@ export async function uncaughtErrorHandler(err: any, req: express.Request, res: // TODO: clean up everything below this - logger.error(err.message, { userInfo }); - logger.error(err.stack, { userInfo }); + logger.error(err.message, { userInfo, requestId: res.locals.requestId }); + logger.error(err.stack, { userInfo, requestId: res.locals.requestId }); try { rollbarServer.warn('Error not handled by error handler', req, userInfo, err); } catch (ex) { - logger.error('Error sending to Rollbar', ex); + logger.error('Error sending to Rollbar', ex, { requestId: res.locals.requestId }); } const errorMessage = 'There was an error processing the request'; diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index 9f2fddefd..19e80d68a 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -2,19 +2,27 @@ import '@jetstream/api-config'; // this gets imported first to ensure as some it import { ENV, logger, pgPool } from '@jetstream/api-config'; import { HTTP, SESSION_EXP_DAYS } from '@jetstream/shared/constants'; import { json, raw, urlencoded } from 'body-parser'; -import * as pgSimple from 'connect-pg-simple'; -import * as cors from 'cors'; -import * as express from 'express'; +import cluster from 'cluster'; +import pgSimple from 'connect-pg-simple'; +import cors from 'cors'; +import express from 'express'; import proxy from 'express-http-proxy'; -import * as session from 'express-session'; -import * as helmet from 'helmet'; -import * as passport from 'passport'; -import * as Auth0Strategy from 'passport-auth0'; +import session from 'express-session'; +import helmet from 'helmet'; +import { cpus } from 'os'; +import passport from 'passport'; +import Auth0Strategy from 'passport-auth0'; import { Strategy as CustomStrategy } from 'passport-custom'; import { join } from 'path'; import { initSocketServer } from './app/controllers/socket.controller'; import { apiRoutes, oauthRoutes, platformEventRoutes, staticAuthenticatedRoutes, testRoutes } from './app/routes'; -import { blockBotByUserAgentMiddleware, logRoute, notFoundMiddleware, setApplicationCookieMiddleware } from './app/routes/route.middleware'; +import { + addContextMiddleware, + blockBotByUserAgentMiddleware, + logRoute, + notFoundMiddleware, + setApplicationCookieMiddleware, +} from './app/routes/route.middleware'; import { blockBotHandler, healthCheck, uncaughtErrorHandler } from './app/utils/response.handlers'; import { environment } from './environments/environment'; @@ -24,184 +32,233 @@ declare module 'express-session' { } } -const pgSession = pgSimple(session); - -const sessionMiddleware = session({ - store: new pgSession({ - pool: pgPool, - tableName: 'sessions', - }), - cookie: { - path: '/', - // httpOnly: true, - secure: environment.production, - maxAge: 1000 * 60 * 60 * 24 * SESSION_EXP_DAYS, - // sameSite: 'strict', - }, - secret: ENV.JETSTREAM_SESSION_SECRET, - resave: false, - saveUninitialized: false, - // This will extend the cookie expiration date if there is a request of any kind to a logged in user - rolling: true, - name: 'sessionid', -}); - -passport.serializeUser(function (user, done) { - done(null, user); -}); - -passport.deserializeUser(function (user, done) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - done(null, user!); -}); - -const passportInitMiddleware = passport.initialize(); -const passportMiddleware = passport.session(); - -const app = express(); -const httpServer = initSocketServer(app, [sessionMiddleware, passportInitMiddleware, passportMiddleware]); - -if (environment.production) { - app.set('trust proxy', 1); // required for environments such as heroku / {render?} -} +// NOTE: render reports more CPUs than are actually available +const CPU_COUNT = Math.min(cpus().length, 3); -// Setup session -app.use(sessionMiddleware); - -// app.use(compression()); -app.use( - helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: [ - "'self'", - '*.google-analytics.com', - '*.google.com', - '*.googleapis.com', - '*.gstatic.com', - '*.rollbar.com', - 'api.amplitude.com', - 'api.cloudinary.com', - ], - baseUri: ["'self'"], - blockAllMixedContent: [], - fontSrc: ["'self'", 'https:', "'unsafe-inline'", 'data:', '*.gstatic.com'], - frameAncestors: ["'self'", '*.google.com'], - imgSrc: [ - "'self'", - 'data:', - '*.cloudinary.com', - '*.ctfassets.net', - '*.documentforce.com', - '*.force.com', - '*.githubusercontent.com', - '*.google-analytics.com', - '*.googletagmanager.com', - '*.googleusercontent.com', - '*.gravatar.com', - '*.gstatic.com', - '*.salesforce.com', - '*.wp.com', - ], - objectSrc: ["'none'"], - scriptSrc: [ - "'self'", - "'sha256-AS526U4qXJy7/SohgsysWUxi77DtcgSmP0hNfTo6/Hs='", // Google Analytics (Docs) - "'sha256-pOkCIUf8FXwCoKWPXTEJAC2XGbyg3ftSrE+IES4aqEY='", // Google Analytics (Next/React) - 'blob:', - '*.google.com', - '*.gstatic.com', - '*.google-analytics.com', - '*.googletagmanager.com', - ], - scriptSrcAttr: ["'none'"], - styleSrc: ["'self'", 'https:', "'unsafe-inline'"], - upgradeInsecureRequests: [], - }, - }, - }) -); +if (ENV.NODE_ENV === 'production' && cluster.isPrimary) { + logger.info(`Number of CPUs is ${CPU_COUNT}`); + logger.info(`Master ${process.pid} is running`); -if (ENV.ENVIRONMENT === 'development') { - /** - * All analytics go through our server instead of directly to amplitude - * This ensures that amplitude is not blocked by various browser tools - */ - app.use('/analytics', cors({ origin: /http:\/\/localhost:[0-9]+$/ }), (req, res) => res.status(200).send('success')); + for (let i = 0; i < CPU_COUNT; i++) { + cluster.fork(); + } + + cluster.on('exit', (worker, code, signal) => { + logger.info(`worker ${worker.process.pid} died, restarting`, { code, signal }); + cluster.fork(); + }); } else { - /** - * All analytics go through our server instead of directly to amplitude - * This ensures that amplitude is not blocked by various browser tools - */ - app.use('/analytics', proxy('https://api.amplitude.com')); -} + logger.info(`Worker ${process.pid} started`); -app.use(blockBotByUserAgentMiddleware); -app.use(setApplicationCookieMiddleware); - -/** Manual test user, skip Auth0 completely */ -passport.use( - 'custom', - new CustomStrategy(function (req, callback) { - if (req.hostname !== 'localhost' || !ENV.EXAMPLE_USER_OVERRIDE || !ENV.EXAMPLE_USER) { - return callback(new Error('Test user not enabled')); - } - - const user = ENV.EXAMPLE_USER; - req.user = user; - callback(null, user); - }) -); - -passport.use( - 'auth0', - new Auth0Strategy( - { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - domain: ENV.AUTH0_DOMAIN!, - clientID: ENV.AUTH0_CLIENT_ID!, - clientSecret: ENV.AUTH0_CLIENT_SECRET!, - callbackURL: `${ENV.JETSTREAM_SERVER_URL}/oauth/callback`, - }, - (accessToken, refreshToken, extraParams, profile, done) => { - // accessToken is the token to call Auth0 API (not needed in the most cases) - // extraParams.id_token has the JSON Web Token - // profile has all the information from the user - return done(null, profile); - } - ) -); - -/** This configuration is used for authorization, not authentication (e.x. link second identity to user) */ -passport.use( - 'auth0-authz', - new Auth0Strategy( - { - domain: ENV.AUTH0_DOMAIN!, - clientID: ENV.AUTH0_CLIENT_ID!, - clientSecret: ENV.AUTH0_CLIENT_SECRET!, - callbackURL: `${ENV.JETSTREAM_SERVER_URL}/oauth/identity/link/callback`, + const pgSession = pgSimple(session); + + const sessionMiddleware = session({ + store: new pgSession({ + pool: pgPool, + tableName: 'sessions', + }), + cookie: { + path: '/', + // httpOnly: true, + secure: environment.production, + maxAge: 1000 * 60 * 60 * 24 * SESSION_EXP_DAYS, + // sameSite: 'strict', }, - (accessToken, refreshToken, extraParams, profile, done) => { - // accessToken is the token to call Auth0 API (not needed in the most cases) - // extraParams.id_token has the JSON Web Token - // profile has all the information from the user - return done(null, profile); - } - ) -); - -app.use(passportInitMiddleware); -app.use(passportMiddleware); - -// proxy must be provided prior to body parser to ensure streaming response -if (ENV.ENVIRONMENT === 'development') { - app.use((req: express.Request, res: express.Response, next: express.NextFunction) => { - if (req.headers.origin?.includes('localhost')) { - res.setHeader('Access-Control-Allow-Origin', req.headers.origin); - res.setHeader( - 'Access-Control-Expose-Headers', - [ + secret: ENV.JETSTREAM_SESSION_SECRET, + resave: false, + saveUninitialized: false, + // This will extend the cookie expiration date if there is a request of any kind to a logged in user + rolling: true, + name: 'sessionid', + }); + + passport.serializeUser(function (user, done) { + done(null, user); + }); + + passport.deserializeUser(function (user, done) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + done(null, user!); + }); + + const passportInitMiddleware = passport.initialize(); + const passportMiddleware = passport.session(); + + const app = express(); + const httpServer = initSocketServer(app, [sessionMiddleware, passportInitMiddleware, passportMiddleware]); + + if (environment.production) { + app.set('trust proxy', 1); // required for environments such as heroku / {render?} + } + + app.use(addContextMiddleware); + + // Setup session + app.use(sessionMiddleware); + + // app.use(compression()); + app.use( + helmet({ + contentSecurityPolicy: { + directives: { + defaultSrc: [ + "'self'", + '*.google-analytics.com', + '*.google.com', + '*.googleapis.com', + '*.gstatic.com', + '*.rollbar.com', + 'api.amplitude.com', + 'api.cloudinary.com', + ], + baseUri: ["'self'"], + blockAllMixedContent: [], + fontSrc: ["'self'", 'https:', "'unsafe-inline'", 'data:', '*.gstatic.com'], + frameAncestors: ["'self'", '*.google.com'], + imgSrc: [ + "'self'", + 'data:', + '*.cloudinary.com', + '*.ctfassets.net', + '*.documentforce.com', + '*.force.com', + '*.githubusercontent.com', + '*.google-analytics.com', + '*.googletagmanager.com', + '*.googleusercontent.com', + '*.gravatar.com', + '*.gstatic.com', + '*.salesforce.com', + '*.wp.com', + ], + objectSrc: ["'none'"], + scriptSrc: [ + "'self'", + "'sha256-AS526U4qXJy7/SohgsysWUxi77DtcgSmP0hNfTo6/Hs='", // Google Analytics (Docs) + "'sha256-pOkCIUf8FXwCoKWPXTEJAC2XGbyg3ftSrE+IES4aqEY='", // Google Analytics (Next/React) + 'blob:', + '*.google.com', + '*.gstatic.com', + '*.google-analytics.com', + '*.googletagmanager.com', + ], + scriptSrcAttr: ["'none'"], + styleSrc: ["'self'", 'https:', "'unsafe-inline'"], + upgradeInsecureRequests: [], + }, + }, + }) + ); + + if (ENV.ENVIRONMENT === 'development') { + /** + * All analytics go through our server instead of directly to amplitude + * This ensures that amplitude is not blocked by various browser tools + */ + app.use('/analytics', cors({ origin: /http:\/\/localhost:[0-9]+$/ }), (req, res) => res.status(200).send('success')); + } else { + /** + * All analytics go through our server instead of directly to amplitude + * This ensures that amplitude is not blocked by various browser tools + */ + app.use('/analytics', proxy('https://api.amplitude.com')); + } + + app.use(blockBotByUserAgentMiddleware); + app.use(setApplicationCookieMiddleware); + + /** Manual test user, skip Auth0 completely */ + passport.use( + 'custom', + new CustomStrategy(function (req, callback) { + if (req.hostname !== 'localhost' || !ENV.EXAMPLE_USER_OVERRIDE || !ENV.EXAMPLE_USER) { + return callback(new Error('Test user not enabled')); + } + + const user = ENV.EXAMPLE_USER; + req.user = user; + callback(null, user); + }) + ); + + passport.use( + 'auth0', + new Auth0Strategy( + { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + domain: ENV.AUTH0_DOMAIN!, + clientID: ENV.AUTH0_CLIENT_ID!, + clientSecret: ENV.AUTH0_CLIENT_SECRET!, + callbackURL: `${ENV.JETSTREAM_SERVER_URL}/oauth/callback`, + }, + (accessToken, refreshToken, extraParams, profile, done) => { + // accessToken is the token to call Auth0 API (not needed in the most cases) + // extraParams.id_token has the JSON Web Token + // profile has all the information from the user + return done(null, profile); + } + ) + ); + + /** This configuration is used for authorization, not authentication (e.x. link second identity to user) */ + passport.use( + 'auth0-authz', + new Auth0Strategy( + { + domain: ENV.AUTH0_DOMAIN!, + clientID: ENV.AUTH0_CLIENT_ID!, + clientSecret: ENV.AUTH0_CLIENT_SECRET!, + callbackURL: `${ENV.JETSTREAM_SERVER_URL}/oauth/identity/link/callback`, + }, + (accessToken, refreshToken, extraParams, profile, done) => { + // accessToken is the token to call Auth0 API (not needed in the most cases) + // extraParams.id_token has the JSON Web Token + // profile has all the information from the user + return done(null, profile); + } + ) + ); + + app.use(passportInitMiddleware); + app.use(passportMiddleware); + + // proxy must be provided prior to body parser to ensure streaming response + if (ENV.ENVIRONMENT === 'development') { + app.use((req: express.Request, res: express.Response, next: express.NextFunction) => { + if (req.headers.origin?.includes('localhost')) { + res.setHeader('Access-Control-Allow-Origin', req.headers.origin); + res.setHeader( + 'Access-Control-Expose-Headers', + [ + HTTP.HEADERS.X_LOGOUT, + HTTP.HEADERS.X_LOGOUT_URL, + HTTP.HEADERS.X_SFDC_ID, + HTTP.HEADERS.X_SFDC_API_VERSION, + HTTP.HEADERS.X_SFDC_ID_TARGET, + HTTP.HEADERS.X_SFDC_API_TARGET_VERSION, + HTTP.HEADERS.X_SFDC_ORG_CONNECTION_ERROR, + HTTP.HEADERS.X_SFDC_Session, + HTTP.HEADERS.X_INCLUDE_CALL_OPTIONS, + HTTP.HEADERS.X_CACHE_RESPONSE, + HTTP.HEADERS.X_CACHE_KEY, + HTTP.HEADERS.X_CACHE_AGE, + HTTP.HEADERS.X_CACHE_EXP, + ].join(', ') + ); + } + next(); + }); + app.options( + '*', + logRoute, + (req: express.Request, res: express.Response, next: express.NextFunction) => { + res.setHeader('Access-Control-Allow-Credentials', 'true'); + res.setHeader('Access-Control-Allow-Origin', '*'); + next(); + }, + cors({ + origin: true, + exposedHeaders: [ HTTP.HEADERS.X_LOGOUT, HTTP.HEADERS.X_LOGOUT_URL, HTTP.HEADERS.X_SFDC_ID, @@ -215,125 +272,91 @@ if (ENV.ENVIRONMENT === 'development') { HTTP.HEADERS.X_CACHE_KEY, HTTP.HEADERS.X_CACHE_AGE, HTTP.HEADERS.X_CACHE_EXP, - ].join(', ') - ); - } - next(); - }); - app.options( - '*', - logRoute, - (req: express.Request, res: express.Response, next: express.NextFunction) => { - res.setHeader('Access-Control-Allow-Credentials', 'true'); - res.setHeader('Access-Control-Allow-Origin', '*'); - next(); - }, - cors({ - origin: true, - exposedHeaders: [ - HTTP.HEADERS.X_LOGOUT, - HTTP.HEADERS.X_LOGOUT_URL, - HTTP.HEADERS.X_SFDC_ID, - HTTP.HEADERS.X_SFDC_API_VERSION, - HTTP.HEADERS.X_SFDC_ID_TARGET, - HTTP.HEADERS.X_SFDC_API_TARGET_VERSION, - HTTP.HEADERS.X_SFDC_ORG_CONNECTION_ERROR, - HTTP.HEADERS.X_SFDC_Session, - HTTP.HEADERS.X_INCLUDE_CALL_OPTIONS, - HTTP.HEADERS.X_CACHE_RESPONSE, - HTTP.HEADERS.X_CACHE_KEY, - HTTP.HEADERS.X_CACHE_AGE, - HTTP.HEADERS.X_CACHE_EXP, - ], - }) - ); - app.use('/platform-event', logRoute, cors({ origin: /http:\/\/localhost:[0-9]+$/ }), platformEventRoutes); -} else { - app.use('/platform-event', logRoute, platformEventRoutes); -} - -app.use(raw({ limit: '30mb', type: ['text/csv'] })); -app.use(raw({ limit: '30mb', type: ['application/zip'] })); -app.use(json({ limit: '20mb', type: ['json', 'application/csp-report'] })); -app.use(urlencoded({ extended: true })); + ], + }) + ); + app.use('/platform-event', logRoute, cors({ origin: /http:\/\/localhost:[0-9]+$/ }), platformEventRoutes); + } else { + app.use('/platform-event', logRoute, platformEventRoutes); + } -app.use('/healthz', healthCheck); -app.use('/api', logRoute, apiRoutes); -app.use('/static', logRoute, staticAuthenticatedRoutes); // these are routes that return files or redirect (e.x. NOT JSON) -app.use('/oauth', logRoute, oauthRoutes); // NOTE: there are also static files with same path + app.use(raw({ limit: '30mb', type: ['text/csv'] })); + app.use(raw({ limit: '30mb', type: ['application/zip'] })); + app.use(json({ limit: '20mb', type: ['json', 'application/csp-report'] })); + app.use(urlencoded({ extended: true })); -if (ENV.ENVIRONMENT !== 'production' || ENV.IS_CI) { - app.use('/test', logRoute, testRoutes); -} + app.use('/healthz', healthCheck); + app.use('/api', logRoute, apiRoutes); + app.use('/static', logRoute, staticAuthenticatedRoutes); // these are routes that return files or redirect (e.x. NOT JSON) + app.use('/oauth', logRoute, oauthRoutes); // NOTE: there are also static files with same path -// const server = app.listen(Number(ENV.PORT), () => { -// logger.info('Listening at http://localhost:' + ENV.PORT); -// }); + if (ENV.ENVIRONMENT !== 'production' || ENV.IS_CI) { + app.use('/test', logRoute, testRoutes); + } -const server = httpServer.listen(Number(ENV.PORT), () => { - logger.info('Listening at http://localhost:' + ENV.PORT); - logger.info('[ENVIRONMENT]: ' + ENV.ENVIRONMENT); -}); + // const server = app.listen(Number(ENV.PORT), () => { + // logger.info('Listening at http://localhost:' + ENV.PORT); + // }); -if (!environment.production) { - app.use(cors({ origin: /http:\/\/localhost:[0-9]+$/ })); -} + const server = httpServer.listen(Number(ENV.PORT), () => { + logger.info('Listening at http://localhost:' + ENV.PORT); + logger.info('[ENVIRONMENT]: ' + ENV.ENVIRONMENT); + }); -app.use('/codicon.ttf', (req: express.Request, res: express.Response) => { - res.sendFile(join(__dirname, './assets/js/monaco/vs/base/browser/ui/codicons/codicon/codicon.ttf'), { maxAge: '1m' }); -}); -app.use('/assets', express.static(join(__dirname, './assets'), { maxAge: '1m' })); -app.use('/fonts', express.static(join(__dirname, './assets/fonts'))); + if (!environment.production) { + app.use(cors({ origin: /http:\/\/localhost:[0-9]+$/ })); + } -if (environment.production || ENV.IS_CI) { - app.use(express.static(join(__dirname, '../landing/exported'))); - app.use(express.static(join(__dirname, '../jetstream'))); + app.use('/codicon.ttf', (req: express.Request, res: express.Response) => { + res.sendFile(join(__dirname, './assets/js/monaco/vs/base/browser/ui/codicons/codicon/codicon.ttf'), { maxAge: '1m' }); + }); + app.use('/assets', express.static(join(__dirname, './assets'), { maxAge: '1m' })); + app.use('/fonts', express.static(join(__dirname, './assets/fonts'))); + app.use(express.static(join(__dirname, '../landing'))); // SERVICE WORKER FOR DOWNLOAD ZIP app.use('/download-zip.sw.js', (req: express.Request, res: express.Response) => { res.sendFile(join(__dirname, '../download-zip-sw/download-zip.sw.js'), { maxAge: '1m' }); }); - app.use('/app', logRoute, (req: express.Request, res: express.Response) => { - res.sendFile(join(__dirname, '../jetstream/index.html')); - }); -} else { - // localhost will only use landing page resources - app.use(express.static(join(__dirname, '../../../dist/apps/landing/exported'))); - app.use('/download-zip.sw.js', (req: express.Request, res: express.Response) => { - res.sendFile(join(__dirname, '../../../dist/apps/download-zip-sw/download-zip.sw.js'), { maxAge: '1m' }); + + if (environment.production || ENV.IS_CI) { + app.use(express.static(join(__dirname, '../jetstream'))); + app.use('/app', (req: express.Request, res: express.Response) => { + res.sendFile(join(__dirname, '../jetstream/index.html')); + }); + } + + /** + * SEND 418 FOR BLOCKED ROUTES THAT ARE PRODUCED BY BOTS + */ + + const BOT_ROUTES = [ + '/_ignition*', + '/*.aspx', + '/*.env*', + '/*.php', + '/*.txt', + '/*.xml', + '/*magento_version', + '/*phpinfo*', + '/*wp-content*', + '/*wp-includes*', + '//feed*', + '/%20', + '/ALFA_DATA*', + '/cgi-bin*', + '/humans.txt', + '/tmp*', + '/view-source*', + '/wp*', + ]; + + BOT_ROUTES.forEach((route) => app.use(route, blockBotHandler)); + + app.use('*', notFoundMiddleware); + app.use(uncaughtErrorHandler); + + server.on('error', (error: Error) => { + logger.error('[SERVER][ERROR]', error.message); + logger.error(error.stack); }); } - -/** - * SEND 418 FOR BLOCKED ROUTES THAT ARE PRODUCED BY BOTS - */ - -const BOT_ROUTES = [ - '/_ignition*', - '/*.aspx', - '/*.env*', - '/*.php', - '/*.txt', - '/*.xml', - '/*magento_version', - '/*phpinfo*', - '/*wp-content*', - '/*wp-includes*', - '//feed*', - '/%20', - '/ALFA_DATA*', - '/cgi-bin*', - '/humans.txt', - '/tmp*', - '/view-source*', - '/wp*', -]; - -BOT_ROUTES.forEach((route) => app.use(route, blockBotHandler)); - -app.use('*', notFoundMiddleware); -app.use(uncaughtErrorHandler); - -server.on('error', (error: Error) => { - logger.error('[SERVER][ERROR]', error.message); - logger.error(error.stack); -}); diff --git a/apps/api/tsconfig.app.json b/apps/api/tsconfig.app.json index fa2ddf45d..ae0dc35c7 100644 --- a/apps/api/tsconfig.app.json +++ b/apps/api/tsconfig.app.json @@ -1,10 +1,10 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "allowSyntheticDefaultImports": true, "outDir": "../../dist/out-tsc", + "module": "commonjs", "types": ["node", "google.accounts", "google.picker", "gapi.auth2", "gapi.client.drive"] }, - "exclude": ["**/*.spec.ts", "**/*.test.ts", "jest.config.ts"], - "include": ["**/*.ts"] + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] } diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json index 3448fd0a6..c1e2dd4e8 100644 --- a/apps/api/tsconfig.json +++ b/apps/api/tsconfig.json @@ -1,11 +1,7 @@ { "extends": "../../tsconfig.base.json", - "compilerOptions": { - "strictNullChecks": true, - "types": ["node", "jest", "express", "google.accounts", "google.picker", "gapi.auth2", "gapi.client.drive"] - }, - "include": [], "files": [], + "include": [], "references": [ { "path": "./tsconfig.app.json" @@ -13,5 +9,8 @@ { "path": "./tsconfig.spec.json" } - ] + ], + "compilerOptions": { + "esModuleInterop": true + } } diff --git a/apps/api/tsconfig.spec.json b/apps/api/tsconfig.spec.json index 148da8555..f6d8ffcc9 100644 --- a/apps/api/tsconfig.spec.json +++ b/apps/api/tsconfig.spec.json @@ -5,5 +5,5 @@ "module": "commonjs", "types": ["jest", "node"] }, - "include": ["**/*.spec.ts", "**/*.test.ts", "**/*.d.ts", "jest.config.ts"] + "include": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts"] } diff --git a/apps/cron-tasks/project.json b/apps/cron-tasks/project.json index ce2743c22..3c58b92d6 100644 --- a/apps/cron-tasks/project.json +++ b/apps/cron-tasks/project.json @@ -5,7 +5,7 @@ "projectType": "application", "targets": { "build": { - "executor": "@nrwl/webpack:webpack", + "executor": "@nx/webpack:webpack", "outputs": ["{options.outputPath}"], "options": { "outputPath": "dist/apps/cron-tasks", @@ -18,12 +18,17 @@ { "entryName": "inactive-account-deletion", "entryPath": "apps/cron-tasks/src/inactive-account-deletion.ts" + }, + { + "entryName": "save-analytics-summary", + "entryPath": "apps/cron-tasks/src/save-analytics-summary.ts" } ], "tsConfig": "apps/cron-tasks/tsconfig.app.json", "assets": [], "target": "node", - "compiler": "tsc" + "compiler": "tsc", + "webpackConfig": "apps/cron-tasks/webpack.config.js" }, "configurations": { "production": { @@ -40,25 +45,21 @@ } }, "serve": { - "executor": "@nrwl/node:node", + "executor": "@nx/js:node", "options": { "buildTarget": "cron-tasks:build", "inspect": true } }, "lint": { - "executor": "@nrwl/linter:eslint", - "outputs": ["{options.outputFile}"], - "options": { - "lintFilePatterns": ["apps/cron-tasks/**/*.ts"] - } + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"] }, "test": { - "executor": "@nrwl/jest:jest", + "executor": "@nx/jest:jest", "outputs": ["{workspaceRoot}/coverage/apps/cron-tasks"], "options": { - "jestConfig": "apps/cron-tasks/jest.config.ts", - "passWithNoTests": true + "jestConfig": "apps/cron-tasks/jest.config.ts" } } }, diff --git a/apps/cron-tasks/src/config/db.config.ts b/apps/cron-tasks/src/config/db.config.ts index 42688b7da..1154e178f 100644 --- a/apps/cron-tasks/src/config/db.config.ts +++ b/apps/cron-tasks/src/config/db.config.ts @@ -11,7 +11,6 @@ if (ENV.PRISMA_DEBUG) { export const prisma = new PrismaClient({ log, - rejectOnNotFound: false, }); export const pgPool = new Pool({ diff --git a/apps/cron-tasks/src/config/env-config.ts b/apps/cron-tasks/src/config/env-config.ts index d667ba20b..5ba31d295 100644 --- a/apps/cron-tasks/src/config/env-config.ts +++ b/apps/cron-tasks/src/config/env-config.ts @@ -35,4 +35,7 @@ export const ENV = { MAILGUN_API_KEY: process.env.MAILGUN_API_KEY, MAILGUN_PUBLIC_KEY: process.env.MAILGUN_PUBLIC_KEY, MAILGUN_WEBHOOK_KEY: process.env.MAILGUN_WEBHOOK_KEY, + + AMPLITUDE_API_KEY: process.env.AMPLITUDE_API_KEY, + AMPLITUDE_SECRET_KEY: process.env.AMPLITUDE_SECRET_KEY, }; diff --git a/apps/cron-tasks/src/save-analytics-summary.ts b/apps/cron-tasks/src/save-analytics-summary.ts new file mode 100644 index 000000000..d28410623 --- /dev/null +++ b/apps/cron-tasks/src/save-analytics-summary.ts @@ -0,0 +1,75 @@ +import { prisma } from './config/db.config'; +import { logger } from './config/logger.config'; +import { getAmplitudeChart } from './utils/amplitude-dashboard-api'; + +const CHART_IDS = { + LOAD: { + YEAR: 'rens73dw', + MONTH: 'iyt2blcf', + WEEK: 'so649gq9', + }, + QUERY: { + YEAR: '33tylew6', + MONTH: 'icruamqk', + WEEK: 'zs6f0dl8', + }, +}; + +(async () => { + try { + logger.info('[ANALYTICS SUMMARY] Exporting data from amplitude'); + + const CHART_LOAD_YEAR = (await getAmplitudeChart(CHART_IDS.LOAD.YEAR)).data.seriesCollapsed[0][0].value; + const CHART_LOAD_MONTH = (await getAmplitudeChart(CHART_IDS.LOAD.MONTH)).data.seriesCollapsed[0][0].value; + const CHART_LOAD_WEEK = (await getAmplitudeChart(CHART_IDS.LOAD.WEEK)).data.seriesCollapsed[0][0].value; + + const CHART_QUERY_YEAR = (await getAmplitudeChart(CHART_IDS.QUERY.YEAR)).data.seriesCollapsed[0][0].value; + const CHART_QUERY_MONTH = (await getAmplitudeChart(CHART_IDS.QUERY.MONTH)).data.seriesCollapsed[0][0].value; + const CHART_QUERY_WEEK = (await getAmplitudeChart(CHART_IDS.QUERY.WEEK)).data.seriesCollapsed[0][0].value; + + logger.info('[ANALYTICS SUMMARY] Saving data to database'); + + const loadResults = await prisma.analyticsSummary.upsert({ + create: { + type: 'LOAD_SUMMARY', + year: CHART_LOAD_YEAR, + month: CHART_LOAD_MONTH, + week: CHART_LOAD_WEEK, + }, + update: { + year: CHART_LOAD_YEAR, + month: CHART_LOAD_MONTH, + week: CHART_LOAD_WEEK, + }, + where: { + type: 'LOAD_SUMMARY', + }, + }); + + logger.info('[ANALYTICS SUMMARY] Load data saved', { loadResults }); + + const queryResults = await prisma.analyticsSummary.upsert({ + create: { + type: 'QUERY_SUMMARY', + year: CHART_QUERY_YEAR, + month: CHART_QUERY_MONTH, + week: CHART_QUERY_WEEK, + }, + update: { + year: CHART_QUERY_YEAR, + month: CHART_QUERY_MONTH, + week: CHART_QUERY_WEEK, + }, + where: { + type: 'QUERY_SUMMARY', + }, + }); + + logger.info('[ANALYTICS SUMMARY] Query data saved', { queryResults }); + + logger.info('[ANALYTICS SUMMARY] Done'); + } catch (ex) { + logger.error('[ANALYTICS SUMMARY][ERROR] %o', ex.message); + logger.error(ex.stack); + } +})(); diff --git a/apps/cron-tasks/src/utils/amplitude-dashboard-api.ts b/apps/cron-tasks/src/utils/amplitude-dashboard-api.ts new file mode 100644 index 000000000..3327dd85b --- /dev/null +++ b/apps/cron-tasks/src/utils/amplitude-dashboard-api.ts @@ -0,0 +1,21 @@ +import axios from 'axios'; +import { ENV } from '../config/env-config'; +import { logger } from '../config/logger.config'; +import { AmplitudeChartResult } from './types'; + +const axiosAuth0 = axios.create({ + baseURL: `https://amplitude.com/api/3`, +}); + +const BASIC_AUTH_HEADER = `Basic ${Buffer.from(`${ENV.AMPLITUDE_API_KEY}:${ENV.AMPLITUDE_SECRET_KEY}`).toString('base64')}`; + +export async function getAmplitudeChart(chartId: string) { + logger.log('info', `getAmplitudeChart: ${chartId}`); + return await axiosAuth0 + .get(`/chart/${chartId}/query`, { + headers: { + Authorization: BASIC_AUTH_HEADER, + }, + }) + .then((result) => result.data); +} diff --git a/apps/cron-tasks/src/utils/types.ts b/apps/cron-tasks/src/utils/types.ts index 11dbbe0b5..363557c50 100644 --- a/apps/cron-tasks/src/utils/types.ts +++ b/apps/cron-tasks/src/utils/types.ts @@ -5,3 +5,50 @@ export interface DeleteResult { orgCount: number | null; localDatabaseId: string | null; } + +export interface AmplitudeChartResult { + data: AmplitudeData; + timeComputed: number; + wasCached: boolean; + cacheFreshness: string; + novaRuntime: number; + novaRequestDuration: number; + novaCost: number; + throttleTime: number; + minSampleRate: number; + transformationIds: any[]; + backend: string; + realtimeDataMissing: boolean; + timedOutRealtimeData: boolean; + missedCacheAndNotComputed: boolean; + partialMergedAndNewUserInformation: boolean; + prunedResult: boolean; + hitChunkGroupByLimit: boolean; + subcluster: number; + millisSinceComputed: number; + earliestServerReceivedTime: number; + queryIds: string[]; +} + +export interface AmplitudeData { + series: AmplitudeSeries[][]; + seriesCollapsed: AmplitudeSeriesCollapsed[][]; + seriesLabels: number[]; + seriesMeta: AmplitudeSeriesMeta[]; + xValues: string[]; +} + +export interface AmplitudeSeries { + setId: string; + value: number; +} + +export interface AmplitudeSeriesCollapsed { + setId: string; + value: number; +} + +export interface AmplitudeSeriesMeta { + segmentIndex: number; + eventIndex: number; +} diff --git a/apps/cron-tasks/webpack.config.js b/apps/cron-tasks/webpack.config.js new file mode 100644 index 000000000..0be4f4f03 --- /dev/null +++ b/apps/cron-tasks/webpack.config.js @@ -0,0 +1,8 @@ +const { composePlugins, withNx } = require('@nx/webpack'); + +// Nx plugins for webpack. +module.exports = composePlugins(withNx(), (config) => { + // Note: This was added by an Nx migration. Webpack builds are required to have a corresponding Webpack config file. + // See: https://nx.dev/recipes/webpack/webpack-config-setup + return config; +}); diff --git a/apps/docs/.eslintrc.json b/apps/docs/.eslintrc.json new file mode 100644 index 000000000..a0f4cb714 --- /dev/null +++ b/apps/docs/.eslintrc.json @@ -0,0 +1,22 @@ +{ + "extends": "../../.eslintrc.json", + "rules": {}, + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "parserOptions": { + "project": ["apps/docs/tsconfig.*?.json"] + }, + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/apps/docs/.gitignore b/apps/docs/.gitignore new file mode 100644 index 000000000..b2d6de306 --- /dev/null +++ b/apps/docs/.gitignore @@ -0,0 +1,20 @@ +# Dependencies +/node_modules + +# Production +/build + +# Generated files +.docusaurus +.cache-loader + +# Misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/apps/docs/README.md b/apps/docs/README.md new file mode 100644 index 000000000..aaba2fa1e --- /dev/null +++ b/apps/docs/README.md @@ -0,0 +1,41 @@ +# Website + +This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. + +### Installation + +``` +$ yarn +``` + +### Local Development + +``` +$ yarn start +``` + +This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. + +### Build + +``` +$ yarn build +``` + +This command generates static content into the `build` directory and can be served using any static contents hosting service. + +### Deployment + +Using SSH: + +``` +$ USE_SSH=true yarn deploy +``` + +Not using SSH: + +``` +$ GIT_USER= yarn deploy +``` + +If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. diff --git a/apps/docs/docs/automation-control/automation-control.md b/apps/docs/docs/automation-control/automation-control.mdx similarity index 97% rename from apps/docs/docs/automation-control/automation-control.md rename to apps/docs/docs/automation-control/automation-control.mdx index 782e6c43e..05f5eb45d 100644 --- a/apps/docs/docs/automation-control/automation-control.md +++ b/apps/docs/docs/automation-control/automation-control.mdx @@ -90,7 +90,7 @@ Exporting will not include any pending changes you have made, it will always mat ### Export as Zip -This option will download a metadata package based on the current state of all metadata in Salesforce. This is a full backup of all the metadata and will allow you to re-deploy the metadata at any point in the future using the [Deploy Metadata > Deploy and Compare Metadata](../deploy/deploy-metadata.md) page and choosing **Upload Metadata Zip**. +This option will download a metadata package based on the current state of all metadata in Salesforce. This is a full backup of all the metadata and will allow you to re-deploy the metadata at any point in the future using the [Deploy Metadata > Deploy and Compare Metadata](../deploy/deploy-metadata.mdx) page and choosing **Upload Metadata Zip**. #### Re-deploying changes diff --git a/apps/docs/docs/deploy/create-object-form.png b/apps/docs/docs/deploy/create-object-form.png new file mode 100644 index 000000000..f0621a772 Binary files /dev/null and b/apps/docs/docs/deploy/create-object-form.png differ diff --git a/apps/docs/docs/deploy/create-object-permissions.png b/apps/docs/docs/deploy/create-object-permissions.png new file mode 100644 index 000000000..da7c1aa84 Binary files /dev/null and b/apps/docs/docs/deploy/create-object-permissions.png differ diff --git a/apps/docs/docs/deploy/create-object-results.png b/apps/docs/docs/deploy/create-object-results.png new file mode 100644 index 000000000..ba4234ec6 Binary files /dev/null and b/apps/docs/docs/deploy/create-object-results.png differ diff --git a/apps/docs/docs/deploy/deploy-fields.md b/apps/docs/docs/deploy/deploy-fields.mdx similarity index 100% rename from apps/docs/docs/deploy/deploy-fields.md rename to apps/docs/docs/deploy/deploy-fields.mdx diff --git a/apps/docs/docs/deploy/deploy-metadata.md b/apps/docs/docs/deploy/deploy-metadata.mdx similarity index 100% rename from apps/docs/docs/deploy/deploy-metadata.md rename to apps/docs/docs/deploy/deploy-metadata.mdx diff --git a/apps/docs/docs/deploy/deploy-object.mdx b/apps/docs/docs/deploy/deploy-object.mdx new file mode 100644 index 000000000..f65ef073a --- /dev/null +++ b/apps/docs/docs/deploy/deploy-object.mdx @@ -0,0 +1,48 @@ +--- +id: deploy-object +title: Create Custom Object +description: Jetstream makes it easy to quickly create a new Custom Object. +keywords: [ + salesforce, + salesforce admin, + salesforce developer, + salesforce automation, + salesforce workbench + create custom object, + update custom object, + field level security, + page layout, + ] +sidebar_label: Create Custom Object +slug: /deploy-object +--- + +## Getting Started + +You can quickly deploy a new Custom Object in Jetstream from the **Create Object and Fields** page. + +Jetstream allows creating one object at a time, usually you would do this prior to creating new custom fields for the object. + +From the **Create Object and Fields** page, click the **Create New Object** button to open the modal to guide you through the process. + +## Configure Permissions + +Choose the profiles and permission sets you would like to assign permissions to, and choose which permissions you would like to apply. + +Create object permissions + +## Configure Object + +Configure your object. Jetstream will automatically populate the object plural label, API name, and Field Name fields for you when you modify the label to make the process simple. You can adjust the automatically populated values as needed. + +Refer to Salesforce documentation on specific details for configuring a new custom object. + +Create object form + +## Review Results + +Once you start the deployment, review the results on the the **Results** tab. + +Once you close the modal, the list of objects will be refreshed and the newly created object will be available for selection to create new custom fields. + +Create object results diff --git a/apps/docs/docs/deploy/formula-evaluator.md b/apps/docs/docs/deploy/formula-evaluator.mdx similarity index 83% rename from apps/docs/docs/deploy/formula-evaluator.md rename to apps/docs/docs/deploy/formula-evaluator.mdx index 6f1891d0f..6f3496250 100644 --- a/apps/docs/docs/deploy/formula-evaluator.md +++ b/apps/docs/docs/deploy/formula-evaluator.mdx @@ -26,6 +26,7 @@ the formula editor comes with some really nice features: - Full-featured editor with syntax highlighting - Auto-complete to help build your formulas - Ability to run and test your formulas to see the outcome +- Ability to test formulas as different users :::note @@ -36,12 +37,13 @@ Some formula functions are not yet supported and some complex formulas may not w ## Evaluating a formula 1. Choose which object you would like to work on -2. Choose how numbers should be treated for fields with no value -3. Choose if you want to write a formula from scratch or if you have an existing field you want to start from +2. Choose which user to evaluate as, your user is the default selected user +3. Choose how numbers should be treated for fields with no value +4. Choose if you want to write a formula from scratch or if you have an existing field you want to start from 1. If you choose an existing field, the formula editor will be populated with the existing formula -4. Write your formula -5. Search and choose a record -6. Click "Test Formula" +5. Write your formula +6. Search and choose a record +7. Click "Test Formula" ### Editor diff --git a/apps/docs/docs/developer/anonymous-apex.md b/apps/docs/docs/developer/anonymous-apex.mdx similarity index 100% rename from apps/docs/docs/developer/anonymous-apex.md rename to apps/docs/docs/developer/anonymous-apex.mdx diff --git a/apps/docs/docs/developer/debug-logs.md b/apps/docs/docs/developer/debug-logs.mdx similarity index 100% rename from apps/docs/docs/developer/debug-logs.md rename to apps/docs/docs/developer/debug-logs.mdx diff --git a/apps/docs/docs/developer/export-object-metadata.md b/apps/docs/docs/developer/export-object-metadata.mdx similarity index 100% rename from apps/docs/docs/developer/export-object-metadata.md rename to apps/docs/docs/developer/export-object-metadata.mdx diff --git a/apps/docs/docs/developer/platform-events.md b/apps/docs/docs/developer/platform-events.mdx similarity index 100% rename from apps/docs/docs/developer/platform-events.md rename to apps/docs/docs/developer/platform-events.mdx diff --git a/apps/docs/docs/developer/salesforce-api.md b/apps/docs/docs/developer/salesforce-api.mdx similarity index 100% rename from apps/docs/docs/developer/salesforce-api.md rename to apps/docs/docs/developer/salesforce-api.mdx diff --git a/apps/docs/docs/getting-started/_org-troubleshooting-table.mdx b/apps/docs/docs/getting-started/_org-troubleshooting-table.mdx index ef1376ff9..818384364 100644 --- a/apps/docs/docs/getting-started/_org-troubleshooting-table.mdx +++ b/apps/docs/docs/getting-started/_org-troubleshooting-table.mdx @@ -9,7 +9,7 @@ export const OAuthSettingsList = ({ children }) => ( export const OAuthSettingsSolution = ({ children }) => (

- OAuth configuration settings can be managed in Setup -> Manage Connected Apps ->{' '} + OAuth configuration settings can be managed in SetupManage Connected Apps →{' '} Find Jetstream and click Edit.

From here, you have the ability to: @@ -30,13 +30,8 @@ export const OAuthSettingsSolution = ({ children }) => ( If you have issues adding your org, here are some likely causes and solutions. -| Problem | Possible Causes | Solution | -| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| You are unable to login, your username and password is not accepted from Salesforce | Your org may have a login restriction to only allow access by logging in using the custom domain. | This setting can be found in Salesforce under `Setup` -> `My Domain` -> `Policies` -> `Prevent login from https://login.salesforce.com`.

If this is set to true, then you will want to use the **Custom Login URL** option and provide custom domain shown on the **Current My Domain URL** on the setup page. | -| You recieve an error message after successfully logging in | | | - -:::important - -Jetstream uses a wide range of IP addresses, so you may need to relax IP address restrictions for the Jetstream Connected App. - -::: +| Problem | Possible Causes | Solution | +| --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| You are unable to login, your username and password is not accepted from Salesforce | Your org may have a login restriction to only allow access by logging in using the custom domain. | This setting can be found in Salesforce under `Setup` → `My Domain` → `Policies` → `Prevent login from https://login.salesforce.com`.

If this is set to true, then you will want to use the **Custom Login URL** option and provide custom domain shown on the **Current My Domain URL** on the setup page. | +| You receive an error message after successfully logging in | | | +| When you click a Salesforce link, you are required to "Choose a Verification Method" to continue to Salesforce. | You may have very strict session settings in Salesforce, such as "High Assurance". | By default, Jetstream uses [Frontdoor](https://help.salesforce.com/s/articleView?id=sf.security_frontdoorjsp.htm&type=5) to login using your existing Jetstream session.

This can be disabled by navigating to your settings and enabling the option to **Don't Auto-Login on Link Clicks**.

Disable Auto-Login | diff --git a/apps/docs/docs/getting-started/feedback.md b/apps/docs/docs/getting-started/feedback.mdx similarity index 100% rename from apps/docs/docs/getting-started/feedback.md rename to apps/docs/docs/getting-started/feedback.mdx diff --git a/apps/docs/docs/getting-started/overview.md b/apps/docs/docs/getting-started/overview.mdx similarity index 85% rename from apps/docs/docs/getting-started/overview.md rename to apps/docs/docs/getting-started/overview.mdx index 3a36bdbb4..1936feafa 100644 --- a/apps/docs/docs/getting-started/overview.md +++ b/apps/docs/docs/getting-started/overview.mdx @@ -7,7 +7,7 @@ sidebar_label: Overview slug: / --- -import OrgTroubleshootingTable from './\_org-troubleshooting-table.mdx'; +import OrgTroubleshootingTable from './_org-troubleshooting-table.mdx'; Jetstream is the most advanced toolkit for working with and administering Salesforce. We know that once you start using Jetstream, you'll wonder how you ever got on without it! @@ -38,6 +38,14 @@ You will be asked to choose an org type which is used to determine the which Sal - **Custom Login URL** - `https://my--domain.my.salesforce.com` +### Jetstream IP Addresses + +If you need to whitelist IP addresses for your org, Jetstream will use one of the following three ip addresses: + +- `3.134.238.10` +- `3.129.111.220` +- `52.15.118.168` + ### Troubleshooting Tips diff --git a/apps/docs/docs/getting-started/settings-dont-auto-login.png b/apps/docs/docs/getting-started/settings-dont-auto-login.png new file mode 100644 index 000000000..a37d000c2 Binary files /dev/null and b/apps/docs/docs/getting-started/settings-dont-auto-login.png differ diff --git a/apps/docs/docs/load/image.png b/apps/docs/docs/load/image.png new file mode 100644 index 000000000..eb0e7b8bf Binary files /dev/null and b/apps/docs/docs/load/image.png differ diff --git a/apps/docs/docs/load/load-attachments.md b/apps/docs/docs/load/load-attachments.mdx similarity index 100% rename from apps/docs/docs/load/load-attachments.md rename to apps/docs/docs/load/load-attachments.mdx diff --git a/apps/docs/docs/load/load-custom-metadata.md b/apps/docs/docs/load/load-custom-metadata.mdx similarity index 99% rename from apps/docs/docs/load/load-custom-metadata.md rename to apps/docs/docs/load/load-custom-metadata.mdx index 171be0e3a..4515bfee3 100644 --- a/apps/docs/docs/load/load-custom-metadata.md +++ b/apps/docs/docs/load/load-custom-metadata.mdx @@ -93,7 +93,7 @@ For relationship fields, you will not be able to related fields like you can wit There are some final options available before loading your data to Salesforce. -### Insert Null Values +### Clear Fields with Blank Values This is enabled and required with Custom Metadata records. **All records will be included with each record, even if they are not mapped**. diff --git a/apps/docs/docs/load/load-with-related.md b/apps/docs/docs/load/load-with-related.mdx similarity index 100% rename from apps/docs/docs/load/load-with-related.md rename to apps/docs/docs/load/load-with-related.mdx diff --git a/apps/docs/docs/load/load.md b/apps/docs/docs/load/load.mdx similarity index 93% rename from apps/docs/docs/load/load.md rename to apps/docs/docs/load/load.mdx index dd2fc3ccf..02dd239d5 100644 --- a/apps/docs/docs/load/load.md +++ b/apps/docs/docs/load/load.mdx @@ -55,6 +55,12 @@ Once you have everything configured, continue to Map Fields. ## Map fields +:::tip + +The easiest way to prepare a file to load is to use the Query tool with the fields you want to load and download the records. This will give you an easy starting point to work from. + +::: + You will need to choose which fields from your CSV should be loaded into Salesforce and you need to map fields from your file to fields in Salesforce. Mapping overview @@ -90,11 +96,13 @@ When you choose a lookup field, you will be presented with a few additional opti - You will need to choose how to handle cases where no related record is found or multiple related records are found with the same matching value. - Map to related -:::tip +### Add a value not included in your file -The easiest way to prepare a file to load is to use the Query tool with the fields you want to load and download the records. This will give you an easy starting point to work from. +You can add a manual mapping for fields that are not in your file by clicking on the `Add Manual Mapping` button. -::: +This will add a row to the mapping table and you will need to select a field and you can provide a value. + +Map using a manual value ## Load data @@ -115,7 +123,7 @@ Some third party integrations, such as Zendesk for Salesforce, will not be notif ::: -### Insert Null Values +### Clear Fields with Blank Values By default, if you have blank values (also called null values) in your input file, they will not modify the value for that field in Salesforce. diff --git a/apps/docs/docs/load/update-records.md b/apps/docs/docs/load/update-records.mdx similarity index 100% rename from apps/docs/docs/load/update-records.md rename to apps/docs/docs/load/update-records.mdx diff --git a/apps/docs/docs/other/other-useful-features.md b/apps/docs/docs/other/other-useful-features.mdx similarity index 74% rename from apps/docs/docs/other/other-useful-features.md rename to apps/docs/docs/other/other-useful-features.mdx index b3c7e0ffb..f9c614798 100644 --- a/apps/docs/docs/other/other-useful-features.md +++ b/apps/docs/docs/other/other-useful-features.mdx @@ -54,6 +54,24 @@ Use the query page to find the record you want to work with. From the query resu After taking any of the actions above, you will be presented with a modal that will show you the record and allow you to edit the record. -If you started out in view mode, you can switch over to edit mode by clicking the buttons at the bottom of the modal. +**From here, you can** + +- Access related records by clicking the link for a lookup field + - you can easily go back to a prior record by clicking the breadcrumb link +- View all child records by clicking the child tab +- Edit or clone the record +- Download the record + +#### Viewing your record View record modal + +#### Viewing child records + +:::tip + +Since there can be a lot of data to fetch here to access these records, the data is cached in your browser for a few days. Click the **Reload Records** button if records were recently added or removed. + +::: + +Viewing child records diff --git a/apps/docs/docs/other/view-record-children.png b/apps/docs/docs/other/view-record-children.png new file mode 100644 index 000000000..671676ad9 Binary files /dev/null and b/apps/docs/docs/other/view-record-children.png differ diff --git a/apps/docs/docs/other/view-record.png b/apps/docs/docs/other/view-record.png index 497da9555..fe8fd8c3e 100644 Binary files a/apps/docs/docs/other/view-record.png and b/apps/docs/docs/other/view-record.png differ diff --git a/apps/docs/docs/permissions/permissions.md b/apps/docs/docs/permissions/permissions.mdx similarity index 100% rename from apps/docs/docs/permissions/permissions.md rename to apps/docs/docs/permissions/permissions.mdx diff --git a/apps/docs/docs/query/bulk-record-actions.png b/apps/docs/docs/query/bulk-record-actions.png new file mode 100644 index 000000000..051f6d57c Binary files /dev/null and b/apps/docs/docs/query/bulk-record-actions.png differ diff --git a/apps/docs/docs/query/column-filter.png b/apps/docs/docs/query/column-filter.png deleted file mode 100644 index decc69497..000000000 Binary files a/apps/docs/docs/query/column-filter.png and /dev/null differ diff --git a/apps/docs/docs/query/column-sidebar.png b/apps/docs/docs/query/column-sidebar.png deleted file mode 100644 index e43d06f54..000000000 Binary files a/apps/docs/docs/query/column-sidebar.png and /dev/null differ diff --git a/apps/docs/docs/query/download-attachments.md b/apps/docs/docs/query/download-attachments.mdx similarity index 100% rename from apps/docs/docs/query/download-attachments.md rename to apps/docs/docs/query/download-attachments.mdx diff --git a/apps/docs/docs/query/query-results-bulk-update-menu.png b/apps/docs/docs/query/query-results-bulk-update-menu.png index 82f926689..fadd95e02 100644 Binary files a/apps/docs/docs/query/query-results-bulk-update-menu.png and b/apps/docs/docs/query/query-results-bulk-update-menu.png differ diff --git a/apps/docs/docs/query/query-results.md b/apps/docs/docs/query/query-results.mdx similarity index 82% rename from apps/docs/docs/query/query-results.md rename to apps/docs/docs/query/query-results.mdx index 585a8a948..4ea1ac4b7 100644 --- a/apps/docs/docs/query/query-results.md +++ b/apps/docs/docs/query/query-results.mdx @@ -52,22 +52,6 @@ In the header of each column is a menu icon, clicking this opens a filter menu t You can enter free-form text or click the checkboxes to show or hide rows that have a specific value in that column. -:::tip - -Column filters can also be adjusted by clicking on the **Filters** side bar on the right-hand section of the table. - -::: - -Column filters - -### Adjusting columns - -Clicking the **Columns** button on the right-hand section of the table will open the column menu, where you can show or hide columns. - -You can also click any column header in the table and drag it to change the column order. - -Column sidebar - ### Viewing child records (subqueries) If your query includes a subquery (child records), the data in each row will show text to indicate the number of records associated to each of the returned records and will be blank if there are no child related records. @@ -83,7 +67,17 @@ On the left side of each record, there are four icons. - The **eye** icon is used to view all the fields for that record. - The **pencil** icon is used to edit the record. - The **copy** icon is used to clone the record and you can make any adjustments to fields prior to saving. -- The **code** icon will turn the record into apex code that you can use to create a record that looks just like the on in your query results. +- The **code** icon will turn the record into Apex code that you can use to create a record that looks just like the on in your query results. + +### Bulk Record Actions + +If you select one or more records, a table menu button at the top of the page will be enabled and within the menu you can: + +1. Delete the selected records +2. Turn the selected records into Apex code +3. Open each record in a new tab within Salesforce. (Make sure you have an active session with the Salesforce instance prior to to selecting this action, as you will not be automatically logged in.) + +Bulk Record Actions ## Adjusting your query @@ -101,7 +95,7 @@ When you download your records, the default option will be to include all record ## Query history -The query history works just like it does on the Query page. Refer to [Query](query.md) page for more details. +The query history works just like it does on the Query page. Refer to [Query](query.mdx) page for more details. ## Downloading records @@ -127,15 +121,21 @@ If you are using **Windows** and choose **CSV**, non-english special characters Download records modal +## Creating a new record + +After you query records, you can create a new record for the queried object by clicking the gear menu icon at the top of the page. + +Creating new record + ## Updating queried records You can update all or some of your queried records without downloading anything from the Query Results. -After you query records, you can access the bulk update menu from the dropdown next to the Download button. +After you query records, you can access the bulk update menu from the gear menu icon at the top of the page. -Accessing bulk update +Accessing bulk update records -The configuration to update records is very similar to [Update records in bulk](../load/update-records.md), you can refer to the options there for additional details. +The configuration to update records is very similar to [Update records in bulk](../load/update-records.mdx), you can refer to the options there for additional details. 1. First, determine which records you want to target by selecting **Which Base Records**. 1. Depending on how many records are in Salesforce and how your query was configured, you may have fewer options to choose from than the screenshot shown below. diff --git a/apps/docs/docs/query/query.md b/apps/docs/docs/query/query.mdx similarity index 100% rename from apps/docs/docs/query/query.md rename to apps/docs/docs/query/query.mdx diff --git a/apps/docs/docusaurus.config.js b/apps/docs/docusaurus.config.js deleted file mode 100644 index ea79426b4..000000000 --- a/apps/docs/docusaurus.config.js +++ /dev/null @@ -1,113 +0,0 @@ -/** @type {import('@docusaurus/types').DocusaurusConfig} */ -module.exports = { - title: 'Jetstream', - tagline: 'Documentation', - url: 'https://docs.getjetstream.app', - baseUrl: '/', - onBrokenLinks: 'throw', - onBrokenMarkdownLinks: 'throw', - favicon: 'img/favicon-inverse.ico', - organizationName: 'jetstream', // Usually your GitHub org/user name. - projectName: 'jetstream', // Usually your repo name. - trailingSlash: false, - /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ - themeConfig: { - algolia: { - appId: '21D7I5RB7N', - apiKey: '16cff3d92b030f175ef9a30f606a221e', - indexName: 'jetstream-docs', - // Optional: see doc section below - contextualSearch: false, - // Optional: Algolia search parameters - searchParameters: {}, - //... other Algolia params - }, - // Example announcement banner - // https://docusaurus.io/docs/api/themes/configuration#announcement-bar - // announcementBar: { - // id: 'support_us', - // content: 'We are looking to revamp our docs, please fill this survey', - // backgroundColor: '#fafbfc', - // textColor: '#091E42', - // isCloseable: false, - // }, - image: - 'https://res.cloudinary.com/getjetstream/image/upload/b_rgb:ffffff,bo_3px_solid_rgb:ffffff,pg_1/v1634516631/public/jetstream-logo-1200w.png', - navbar: { - logo: { - alt: 'Jetstream logo', - src: 'img/jetstream-logo.svg', - srcDark: 'img/jetstream-logo-inverse.svg', - }, - items: [ - { - href: 'https://getjetstream.app', - label: 'Jetstream', - position: 'right', - }, - ], - }, - /** @type {import('@docusaurus/theme-common').Footer} */ - footer: { - style: 'dark', - links: [ - { - title: 'Jetstream', - items: [ - { - href: 'https://getjetstream.app', - label: 'Jetstream', - }, - { - href: 'mailto:support@getjetstream.app', - label: 'Contact Us', - }, - ], - }, - {}, - { - title: 'Legal', - items: [ - { - href: 'https://getjetstream.app/terms-of-service/', - label: 'Terms of Service', - }, - { - href: 'https://getjetstream.app/subprocessors/', - label: 'Data Sub-Processors', - }, - { - href: 'https://getjetstream.app/privacy/', - label: 'Privacy Policy', - }, - ], - }, - ], - copyright: `Copyright © ${new Date().getFullYear()} Jetstream.`, - }, - }, - presets: [ - [ - '@docusaurus/preset-classic', - /** @type {import('@docusaurus/preset-classic').Options} */ - ({ - docs: { - sidebarPath: require.resolve('./sidebars.js'), - sidebarCollapsed: false, - routeBasePath: '/', - }, - gtag: { - anonymizeIP: true, - trackingID: 'G-GZJ9QQTK44', - }, - theme: { - customCss: require.resolve('./src/css/custom.css'), - }, - sitemap: { - changefreq: 'weekly', - priority: 0.5, - }, - }), - ], - ], -}; diff --git a/apps/docs/docusaurus.config.ts b/apps/docs/docusaurus.config.ts new file mode 100644 index 000000000..ffbbb34b8 --- /dev/null +++ b/apps/docs/docusaurus.config.ts @@ -0,0 +1,138 @@ +import type * as Preset from '@docusaurus/preset-classic'; +import type { Config, } from '@docusaurus/types'; + +// const config: Config = { +// title: 'My Site', +// favicon: 'img/favicon.ico', +// presets: [ +// [ +// 'classic', +// { +// /* Your preset config here */ +// } satisfies Preset.Options, +// ], +// ], + +// themeConfig: { +// /* Your theme config here */ +// } satisfies Preset.ThemeConfig, +// }; + + +const config: Config = { + title: 'Jetstream', + tagline: 'Documentation', + url: 'https://docs.getjetstream.app', + baseUrl: '/', + onBrokenLinks: 'throw', + onBrokenMarkdownLinks: 'throw', + favicon: 'img/favicon-inverse.ico', + organizationName: 'jetstream', // Usually your GitHub org/user name. + projectName: 'jetstream', // Usually your repo name. + trailingSlash: false, + i18n: { + defaultLocale: 'en', + locales: ['en'], + }, + presets: [ + [ + '@docusaurus/preset-classic', + { + docs: { + sidebarPath: 'sidebars.ts', + sidebarCollapsed: false, + routeBasePath: '/', + editUrl: 'https://github.com/jetstreamapp/jetstream/tree/main/apps/docs/', + }, + gtag: { + anonymizeIP: true, + trackingID: 'G-GZJ9QQTK44', + }, + theme: { + customCss: ['./src/css/custom.css'], + }, + sitemap: { + changefreq: 'weekly' as any, // FIXME: figure out how to use enum + priority: 0.5, + }, + } satisfies Preset.Options, + ], + ], + themeConfig: + { + algolia: { + appId: '21D7I5RB7N', + apiKey: '16cff3d92b030f175ef9a30f606a221e', + indexName: 'jetstream-docs', + // Optional: see doc section below + contextualSearch: false, + // Optional: Algolia search parameters + searchParameters: {}, + //... other Algolia params + }, + // Example announcement banner + // https://docusaurus.io/docs/api/themes/configuration#announcement-bar + // announcementBar: { + // id: 'support_us', + // content: 'We are looking to revamp our docs, please fill this survey', + // backgroundColor: '#fafbfc', + // textColor: '#091E42', + // isCloseable: false, + // }, + image: + 'https://res.cloudinary.com/getjetstream/image/upload/b_rgb:ffffff,bo_3px_solid_rgb:ffffff,pg_1/v1634516631/public/jetstream-logo-1200w.png', + navbar: { + logo: { + alt: 'Jetstream logo', + src: 'img/jetstream-logo.svg', + srcDark: 'img/jetstream-logo-inverse.svg', + }, + items: [ + { + href: 'https://getjetstream.app', + label: 'Jetstream', + position: 'right', + }, + ], + }, + footer: { + style: 'dark', + links: [ + { + title: 'Jetstream', + items: [ + { + href: 'https://getjetstream.app', + label: 'Jetstream', + }, + { + href: 'mailto:support@getjetstream.app', + label: 'Contact Us', + }, + ], + }, + {}, + { + title: 'Legal', + items: [ + { + href: 'https://getjetstream.app/terms-of-service/', + label: 'Terms of Service', + }, + { + href: 'https://getjetstream.app/subprocessors/', + label: 'Data Sub-Processors', + }, + { + href: 'https://getjetstream.app/privacy/', + label: 'Privacy Policy', + }, + ], + }, + ], + copyright: `Copyright © ${new Date().getFullYear()} Jetstream.`, + }, + } satisfies Preset.ThemeConfig, +}; + +export default config; diff --git a/apps/docs/package.json b/apps/docs/package.json new file mode 100644 index 000000000..578e65fb0 --- /dev/null +++ b/apps/docs/package.json @@ -0,0 +1,48 @@ +{ + "name": "docs", + "version": "0.0.0", + "private": true, + "scripts": { + "docusaurus": "docusaurus", + "start": "docusaurus start", + "build": "docusaurus build", + "swizzle": "docusaurus swizzle", + "deploy": "docusaurus deploy", + "clear": "docusaurus clear", + "serve": "docusaurus serve", + "write-translations": "docusaurus write-translations", + "write-heading-ids": "docusaurus write-heading-ids" + }, + "dependencies": { + "@docusaurus/core": "^3.0.1", + "@docusaurus/plugin-ideal-image": "^3.0.1", + "@docusaurus/preset-classic": "^3.0.1", + "@docusaurus/theme-search-algolia": "^3.0.1", + "@mdx-js/react": "^3.0.0", + "clsx": "^2.0.0", + "prism-react-renderer": "^2.3.1", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@docusaurus/module-type-aliases": "^3.0.1", + "@docusaurus/tsconfig": "^3.0.1", + "@docusaurus/types": "^3.0.1", + "typescript": "^5.3.3" + }, + "browserslist": { + "production": [ + ">0.5%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "engines": { + "node": ">=18.0" + } +} diff --git a/apps/docs/sidebars.js b/apps/docs/sidebars.ts similarity index 84% rename from apps/docs/sidebars.js rename to apps/docs/sidebars.ts index 17e59ba3d..8a3f830a2 100644 --- a/apps/docs/sidebars.js +++ b/apps/docs/sidebars.ts @@ -1,5 +1,4 @@ -module.exports = { - /** @type {import('@docusaurus/preset-classic').Si} */ +const sidebar = { sidebar: [ { type: 'category', @@ -21,7 +20,7 @@ module.exports = { { type: 'category', label: 'Deploy Metadata', - items: ['deploy/deploy-metadata', 'deploy/deploy-fields', 'deploy/formula-evaluator'], + items: ['deploy/deploy-metadata', 'deploy/deploy-object', 'deploy/deploy-fields', 'deploy/formula-evaluator'], }, { type: 'category', @@ -37,3 +36,5 @@ module.exports = { 'other/other-useful-features', ], }; + +export default sidebar; diff --git a/apps/docs/tsconfig.json b/apps/docs/tsconfig.json new file mode 100644 index 000000000..d250afaed --- /dev/null +++ b/apps/docs/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "@docusaurus/tsconfig", + "compilerOptions": { + "baseUrl": "." + } +} diff --git a/apps/docs/yarn.lock b/apps/docs/yarn.lock new file mode 100644 index 000000000..c95051e83 --- /dev/null +++ b/apps/docs/yarn.lock @@ -0,0 +1,9591 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@algolia/autocomplete-core@1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz#1d56482a768c33aae0868c8533049e02e8961be7" + integrity sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw== + dependencies: + "@algolia/autocomplete-plugin-algolia-insights" "1.9.3" + "@algolia/autocomplete-shared" "1.9.3" + +"@algolia/autocomplete-plugin-algolia-insights@1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz#9b7f8641052c8ead6d66c1623d444cbe19dde587" + integrity sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg== + dependencies: + "@algolia/autocomplete-shared" "1.9.3" + +"@algolia/autocomplete-preset-algolia@1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz#64cca4a4304cfcad2cf730e83067e0c1b2f485da" + integrity sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA== + dependencies: + "@algolia/autocomplete-shared" "1.9.3" + +"@algolia/autocomplete-shared@1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz#2e22e830d36f0a9cf2c0ccd3c7f6d59435b77dfa" + integrity sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ== + +"@algolia/cache-browser-local-storage@4.22.0": + version "4.22.0" + resolved "https://registry.yarnpkg.com/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.22.0.tgz#548e3f9524988bbe0c14b7fc7b2a66335520eeb7" + integrity sha512-uZ1uZMLDZb4qODLfTSNHxSi4fH9RdrQf7DXEzW01dS8XK7QFtFh29N5NGKa9S+Yudf1vUMIF+/RiL4i/J0pWlQ== + dependencies: + "@algolia/cache-common" "4.22.0" + +"@algolia/cache-common@4.22.0": + version "4.22.0" + resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.22.0.tgz#83d6111caac74a71bebe5fc050a3b64f3e45d037" + integrity sha512-TPwUMlIGPN16eW67qamNQUmxNiGHg/WBqWcrOoCddhqNTqGDPVqmgfaM85LPbt24t3r1z0zEz/tdsmuq3Q6oaA== + +"@algolia/cache-in-memory@4.22.0": + version "4.22.0" + resolved "https://registry.yarnpkg.com/@algolia/cache-in-memory/-/cache-in-memory-4.22.0.tgz#ff86b08d8c80a9402f39e5c64cef2ba8299bbe1d" + integrity sha512-kf4Cio9NpPjzp1+uXQgL4jsMDeck7MP89BYThSvXSjf2A6qV/0KeqQf90TL2ECS02ovLOBXkk98P7qVarM+zGA== + dependencies: + "@algolia/cache-common" "4.22.0" + +"@algolia/client-account@4.22.0": + version "4.22.0" + resolved "https://registry.yarnpkg.com/@algolia/client-account/-/client-account-4.22.0.tgz#d7fa001dc062dca446f0620281fc0cec7c850487" + integrity sha512-Bjb5UXpWmJT+yGWiqAJL0prkENyEZTBzdC+N1vBuHjwIJcjLMjPB6j1hNBRbT12Lmwi55uzqeMIKS69w+0aPzA== + dependencies: + "@algolia/client-common" "4.22.0" + "@algolia/client-search" "4.22.0" + "@algolia/transporter" "4.22.0" + +"@algolia/client-analytics@4.22.0": + version "4.22.0" + resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-4.22.0.tgz#ea10e73d649aa1b9a1a25a786300d241fd4ad0d1" + integrity sha512-os2K+kHUcwwRa4ArFl5p/3YbF9lN3TLOPkbXXXxOvDpqFh62n9IRZuzfxpHxMPKAQS3Et1s0BkKavnNP02E9Hg== + dependencies: + "@algolia/client-common" "4.22.0" + "@algolia/client-search" "4.22.0" + "@algolia/requester-common" "4.22.0" + "@algolia/transporter" "4.22.0" + +"@algolia/client-common@4.22.0": + version "4.22.0" + resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-4.22.0.tgz#4bf298acec78fa988a5b829748e6c488b8a6b570" + integrity sha512-BlbkF4qXVWuwTmYxVWvqtatCR3lzXwxx628p1wj1Q7QP2+LsTmGt1DiUYRuy9jG7iMsnlExby6kRMOOlbhv2Ag== + dependencies: + "@algolia/requester-common" "4.22.0" + "@algolia/transporter" "4.22.0" + +"@algolia/client-personalization@4.22.0": + version "4.22.0" + resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-4.22.0.tgz#210c7d196b3c31da45e16db6ed98a7594fcf5e1c" + integrity sha512-pEOftCxeBdG5pL97WngOBi9w5Vxr5KCV2j2D+xMVZH8MuU/JX7CglDSDDb0ffQWYqcUN+40Ry+xtXEYaGXTGow== + dependencies: + "@algolia/client-common" "4.22.0" + "@algolia/requester-common" "4.22.0" + "@algolia/transporter" "4.22.0" + +"@algolia/client-search@4.22.0": + version "4.22.0" + resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.22.0.tgz#1113332cf973ce69067b741a17e8f798d71e07db" + integrity sha512-bn4qQiIdRPBGCwsNuuqB8rdHhGKKWIij9OqidM1UkQxnSG8yzxHdb7CujM30pvp5EnV7jTqDZRbxacbjYVW20Q== + dependencies: + "@algolia/client-common" "4.22.0" + "@algolia/requester-common" "4.22.0" + "@algolia/transporter" "4.22.0" + +"@algolia/events@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@algolia/events/-/events-4.0.1.tgz#fd39e7477e7bc703d7f893b556f676c032af3950" + integrity sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ== + +"@algolia/logger-common@4.22.0": + version "4.22.0" + resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.22.0.tgz#f9498729ca5b0e9c0bd1b8dd729edd91ddd02b5c" + integrity sha512-HMUQTID0ucxNCXs5d1eBJ5q/HuKg8rFVE/vOiLaM4Abfeq1YnTtGV3+rFEhOPWhRQxNDd+YHa4q864IMc0zHpQ== + +"@algolia/logger-console@4.22.0": + version "4.22.0" + resolved "https://registry.yarnpkg.com/@algolia/logger-console/-/logger-console-4.22.0.tgz#52e62b98fc01b40d6677b0ddf656b342e89f13c2" + integrity sha512-7JKb6hgcY64H7CRm3u6DRAiiEVXMvCJV5gRE672QFOUgDxo4aiDpfU61g6Uzy8NKjlEzHMmgG4e2fklELmPXhQ== + dependencies: + "@algolia/logger-common" "4.22.0" + +"@algolia/requester-browser-xhr@4.22.0": + version "4.22.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.22.0.tgz#ca16e4c6860458477a00b440a407c81591f14b8a" + integrity sha512-BHfv1h7P9/SyvcDJDaRuIwDu2yrDLlXlYmjvaLZTtPw6Ok/ZVhBR55JqW832XN/Fsl6k3LjdkYHHR7xnsa5Wvg== + dependencies: + "@algolia/requester-common" "4.22.0" + +"@algolia/requester-common@4.22.0": + version "4.22.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.22.0.tgz#d7a8283f5b77550eeab353c571a6566adf552fa7" + integrity sha512-Y9cEH/cKjIIZgzvI1aI0ARdtR/xRrOR13g5psCxkdhpgRN0Vcorx+zePhmAa4jdQNqexpxtkUdcKYugBzMZJgQ== + +"@algolia/requester-node-http@4.22.0": + version "4.22.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-4.22.0.tgz#41d5e7d5dc7adb930e7fe8dcd9d39bfc378cc5f5" + integrity sha512-8xHoGpxVhz3u2MYIieHIB6MsnX+vfd5PS4REgglejJ6lPigftRhTdBCToe6zbwq4p0anZXjjPDvNWMlgK2+xYA== + dependencies: + "@algolia/requester-common" "4.22.0" + +"@algolia/transporter@4.22.0": + version "4.22.0" + resolved "https://registry.yarnpkg.com/@algolia/transporter/-/transporter-4.22.0.tgz#733385f6457408228d2a4d7a4fe4e2b1599a5d33" + integrity sha512-ieO1k8x2o77GNvOoC+vAkFKppydQSVfbjM3YrSjLmgywiBejPTvU1R1nEvG59JIIUvtSLrZsLGPkd6vL14zopA== + dependencies: + "@algolia/cache-common" "4.22.0" + "@algolia/logger-common" "4.22.0" + "@algolia/requester-common" "4.22.0" + +"@ampproject/remapping@^2.2.0": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" + integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.22.5", "@babel/code-frame@^7.8.3": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.5.tgz#234d98e1551960604f1246e6475891a570ad5658" + integrity sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ== + dependencies: + "@babel/highlight" "^7.22.5" + +"@babel/code-frame@^7.22.13", "@babel/code-frame@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244" + integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA== + dependencies: + "@babel/highlight" "^7.23.4" + chalk "^2.4.2" + +"@babel/compat-data@^7.22.5", "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.22.9": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.9.tgz#71cdb00a1ce3a329ce4cbec3a44f9fef35669730" + integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ== + +"@babel/compat-data@^7.23.3", "@babel/compat-data@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.5.tgz#ffb878728bb6bdcb6f4510aa51b1be9afb8cfd98" + integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw== + +"@babel/core@^7.19.6": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.9.tgz#bd96492c68822198f33e8a256061da3cf391f58f" + integrity sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.22.5" + "@babel/generator" "^7.22.9" + "@babel/helper-compilation-targets" "^7.22.9" + "@babel/helper-module-transforms" "^7.22.9" + "@babel/helpers" "^7.22.6" + "@babel/parser" "^7.22.7" + "@babel/template" "^7.22.5" + "@babel/traverse" "^7.22.8" + "@babel/types" "^7.22.5" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.2" + semver "^6.3.1" + +"@babel/core@^7.23.3": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.6.tgz#8be77cd77c55baadcc1eae1c33df90ab6d2151d4" + integrity sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helpers" "^7.23.6" + "@babel/parser" "^7.23.6" + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.23.6" + "@babel/types" "^7.23.6" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.22.7", "@babel/generator@^7.22.9": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.9.tgz#572ecfa7a31002fa1de2a9d91621fd895da8493d" + integrity sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw== + dependencies: + "@babel/types" "^7.22.5" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + +"@babel/generator@^7.23.3", "@babel/generator@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.6.tgz#9e1fca4811c77a10580d17d26b57b036133f3c2e" + integrity sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw== + dependencies: + "@babel/types" "^7.23.6" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + +"@babel/helper-annotate-as-pure@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" + integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-builder-binary-assignment-operator-visitor@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz#5426b109cf3ad47b91120f8328d8ab1be8b0b956" + integrity sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw== + dependencies: + "@babel/types" "^7.22.15" + +"@babel/helper-builder-binary-assignment-operator-visitor@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.5.tgz#a3f4758efdd0190d8927fcffd261755937c71878" + integrity sha512-m1EP3lVOPptR+2DwD125gziZNcmoNSHGmJROKoy87loWUQyJaVXDgpmruWqDARZSmtYQ+Dl25okU8+qhVzuykw== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-compilation-targets@^7.22.15", "@babel/helper-compilation-targets@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" + integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== + dependencies: + "@babel/compat-data" "^7.23.5" + "@babel/helper-validator-option" "^7.23.5" + browserslist "^4.22.2" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-compilation-targets@^7.22.5", "@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.22.9": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.9.tgz#f9d0a7aaaa7cd32a3f31c9316a69f5a9bcacb892" + integrity sha512-7qYrNM6HjpnPHJbopxmb8hSPoZ0gsX8IvUS32JGVoy+pU9e5N0nLr1VjJoR6kA4d9dmGLxNYOjeB8sUDal2WMw== + dependencies: + "@babel/compat-data" "^7.22.9" + "@babel/helper-validator-option" "^7.22.5" + browserslist "^4.21.9" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-create-class-features-plugin@^7.22.15", "@babel/helper-create-class-features-plugin@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.6.tgz#b04d915ce92ce363666f816a884cdcfc9be04953" + integrity sha512-cBXU1vZni/CpGF29iTu4YRbOZt3Wat6zCoMDxRF1MayiEc4URxOj31tT65HUM0CRpMowA3HCJaAOVOUnMf96cw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-member-expression-to-functions" "^7.23.0" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.20" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + semver "^6.3.1" + +"@babel/helper-create-class-features-plugin@^7.22.5", "@babel/helper-create-class-features-plugin@^7.22.9": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.9.tgz#c36ea240bb3348f942f08b0fbe28d6d979fab236" + integrity sha512-Pwyi89uO4YrGKxL/eNJ8lfEH55DnRloGPOseaA8NFNL6jAUnn+KccaISiFazCj5IolPPDjGSdzQzXVzODVRqUQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-function-name" "^7.22.5" + "@babel/helper-member-expression-to-functions" "^7.22.5" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + semver "^6.3.1" + +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.5": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.9.tgz#9d8e61a8d9366fe66198f57c40565663de0825f6" + integrity sha512-+svjVa/tFwsNSG4NEy1h85+HQ5imbT92Q5/bgtS7P0GTQlP8WuFdqsiABmQouhiFGyV66oGxZFpeYHza1rNsKw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + regexpu-core "^5.3.1" + semver "^6.3.1" + +"@babel/helper-create-regexp-features-plugin@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz#5ee90093914ea09639b01c711db0d6775e558be1" + integrity sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + regexpu-core "^5.3.1" + semver "^6.3.1" + +"@babel/helper-define-polyfill-provider@^0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.2.tgz#82c825cadeeeee7aad237618ebbe8fa1710015d7" + integrity sha512-k0qnnOqHn5dK9pZpfD5XXZ9SojAITdCKRn2Lp6rnDGzIbaP0rHyMPk/4wsSxVBVz4RfN0q6VpXWP2pDGIoQ7hw== + dependencies: + "@babel/helper-compilation-targets" "^7.22.6" + "@babel/helper-plugin-utils" "^7.22.5" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + +"@babel/helper-define-polyfill-provider@^0.4.4": + version "0.4.4" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz#64df615451cb30e94b59a9696022cffac9a10088" + integrity sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA== + dependencies: + "@babel/helper-compilation-targets" "^7.22.6" + "@babel/helper-plugin-utils" "^7.22.5" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + +"@babel/helper-environment-visitor@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== + +"@babel/helper-environment-visitor@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98" + integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q== + +"@babel/helper-function-name@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be" + integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ== + dependencies: + "@babel/template" "^7.22.5" + "@babel/types" "^7.22.5" + +"@babel/helper-function-name@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" + integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== + dependencies: + "@babel/template" "^7.22.15" + "@babel/types" "^7.23.0" + +"@babel/helper-hoist-variables@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" + integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-member-expression-to-functions@^7.22.15", "@babel/helper-member-expression-to-functions@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz#9263e88cc5e41d39ec18c9a3e0eced59a3e7d366" + integrity sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA== + dependencies: + "@babel/types" "^7.23.0" + +"@babel/helper-member-expression-to-functions@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz#0a7c56117cad3372fbf8d2fb4bf8f8d64a1e76b2" + integrity sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-module-imports@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" + integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== + dependencies: + "@babel/types" "^7.22.15" + +"@babel/helper-module-imports@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz#1a8f4c9f4027d23f520bd76b364d44434a72660c" + integrity sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-module-transforms@^7.22.5", "@babel/helper-module-transforms@^7.22.9": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz#92dfcb1fbbb2bc62529024f72d942a8c97142129" + integrity sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ== + dependencies: + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-module-imports" "^7.22.5" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/helper-validator-identifier" "^7.22.5" + +"@babel/helper-module-transforms@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz#d7d12c3c5d30af5b3c0fcab2a6d5217773e2d0f1" + integrity sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/helper-validator-identifier" "^7.22.20" + +"@babel/helper-optimise-call-expression@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e" + integrity sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" + integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== + +"@babel/helper-remap-async-to-generator@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz#7b68e1cb4fa964d2996fd063723fb48eca8498e0" + integrity sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-wrap-function" "^7.22.20" + +"@babel/helper-remap-async-to-generator@^7.22.5": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.9.tgz#53a25b7484e722d7efb9c350c75c032d4628de82" + integrity sha512-8WWC4oR4Px+tr+Fp0X3RHDVfINGpF3ad1HIbrc8A77epiR6eMMc6jsgozkzT2uDiOOdoS9cLIQ+XD2XvI2WSmQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-wrap-function" "^7.22.9" + +"@babel/helper-replace-supers@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz#e37d367123ca98fe455a9887734ed2e16eb7a793" + integrity sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-member-expression-to-functions" "^7.22.15" + "@babel/helper-optimise-call-expression" "^7.22.5" + +"@babel/helper-replace-supers@^7.22.5", "@babel/helper-replace-supers@^7.22.9": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.9.tgz#cbdc27d6d8d18cd22c81ae4293765a5d9afd0779" + integrity sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg== + dependencies: + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-member-expression-to-functions" "^7.22.5" + "@babel/helper-optimise-call-expression" "^7.22.5" + +"@babel/helper-simple-access@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" + integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-skip-transparent-expression-wrappers@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz#007f15240b5751c537c40e77abb4e89eeaaa8847" + integrity sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-split-export-declaration@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" + integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-string-parser@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" + integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== + +"@babel/helper-string-parser@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83" + integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ== + +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + +"@babel/helper-validator-identifier@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193" + integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ== + +"@babel/helper-validator-option@^7.22.15", "@babel/helper-validator-option@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" + integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== + +"@babel/helper-validator-option@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz#de52000a15a177413c8234fa3a8af4ee8102d0ac" + integrity sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw== + +"@babel/helper-wrap-function@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz#15352b0b9bfb10fc9c76f79f6342c00e3411a569" + integrity sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw== + dependencies: + "@babel/helper-function-name" "^7.22.5" + "@babel/template" "^7.22.15" + "@babel/types" "^7.22.19" + +"@babel/helper-wrap-function@^7.22.9": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.9.tgz#189937248c45b0182c1dcf32f3444ca153944cb9" + integrity sha512-sZ+QzfauuUEfxSEjKFmi3qDSHgLsTPK/pEpoD/qonZKOtTPTLbf59oabPQ4rKekt9lFcj/hTZaOhWwFYrgjk+Q== + dependencies: + "@babel/helper-function-name" "^7.22.5" + "@babel/template" "^7.22.5" + "@babel/types" "^7.22.5" + +"@babel/helpers@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.6.tgz#8e61d3395a4f0c5a8060f309fb008200969b5ecd" + integrity sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA== + dependencies: + "@babel/template" "^7.22.5" + "@babel/traverse" "^7.22.6" + "@babel/types" "^7.22.5" + +"@babel/helpers@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.6.tgz#d03af2ee5fb34691eec0cda90f5ecbb4d4da145a" + integrity sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA== + dependencies: + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.23.6" + "@babel/types" "^7.23.6" + +"@babel/highlight@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.5.tgz#aa6c05c5407a67ebce408162b7ede789b4d22031" + integrity sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw== + dependencies: + "@babel/helper-validator-identifier" "^7.22.5" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/highlight@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b" + integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + +"@babel/parser@^7.22.15", "@babel/parser@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.6.tgz#ba1c9e512bda72a47e285ae42aff9d2a635a9e3b" + integrity sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ== + +"@babel/parser@^7.22.5", "@babel/parser@^7.22.7": + version "7.22.7" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.7.tgz#df8cf085ce92ddbdbf668a7f186ce848c9036cae" + integrity sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q== + +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz#87245a21cd69a73b0b81bcda98d443d6df08f05e" + integrity sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz#5cd1c87ba9380d0afb78469292c954fee5d2411a" + integrity sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz#fef09f9499b1f1c930da8a0c419db42167d792ca" + integrity sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/plugin-transform-optional-chaining" "^7.22.5" + +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz#f6652bb16b94f8f9c20c50941e16e9756898dc5d" + integrity sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/plugin-transform-optional-chaining" "^7.23.3" + +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.3.tgz#20c60d4639d18f7da8602548512e9d3a4c8d7098" + integrity sha512-XaJak1qcityzrX0/IU5nKHb34VaibwP3saKqG6a/tppelgllOH13LUann4ZCIBcVOeE6H18K4Vx9QKkVww3z/w== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": + version "7.21.0-placeholder-for-preset-env.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" + integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== + +"@babel/plugin-proposal-unicode-property-regex@^7.4.4": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz#af613d2cd5e643643b65cded64207b15c85cb78e" + integrity sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-dynamic-import@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" + integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-export-namespace-from@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" + integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-import-assertions@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz#07d252e2aa0bc6125567f742cd58619cb14dce98" + integrity sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-syntax-import-assertions@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz#9c05a7f592982aff1a2768260ad84bcd3f0c77fc" + integrity sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-syntax-import-attributes@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz#ab840248d834410b829f569f5262b9e517555ecb" + integrity sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-syntax-import-attributes@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz#992aee922cf04512461d7dae3ff6951b90a2dc06" + integrity sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz#a6b68e84fb76e759fc3b93e901876ffabbe1d918" + integrity sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-syntax-jsx@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz#8f2e4f8a9b5f9aa16067e142c1ac9cd9f810f473" + integrity sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz#aac8d383b062c5072c647a31ef990c1d0af90272" + integrity sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-syntax-typescript@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz#24f460c85dbbc983cd2b9c4994178bcc01df958f" + integrity sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-syntax-unicode-sets-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357" + integrity sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-arrow-functions@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz#e5ba566d0c58a5b2ba2a8b795450641950b71958" + integrity sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-arrow-functions@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz#94c6dcfd731af90f27a79509f9ab7fb2120fc38b" + integrity sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-async-generator-functions@^7.22.7": + version "7.22.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.7.tgz#053e76c0a903b72b573cb1ab7d6882174d460a1b" + integrity sha512-7HmE7pk/Fmke45TODvxvkxRMV9RazV+ZZzhOL9AG8G29TLrr3jkjwF7uJfxZ30EoXpO+LJkq4oA8NjO2DTnEDg== + dependencies: + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-remap-async-to-generator" "^7.22.5" + "@babel/plugin-syntax-async-generators" "^7.8.4" + +"@babel/plugin-transform-async-generator-functions@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.4.tgz#93ac8e3531f347fba519b4703f9ff2a75c6ae27a" + integrity sha512-efdkfPhHYTtn0G6n2ddrESE91fgXxjlqLsnUtPWnJs4a4mZIbUaK7ffqKIIUKXSHwcDvaCVX6GXkaJJFqtX7jw== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-remap-async-to-generator" "^7.22.20" + "@babel/plugin-syntax-async-generators" "^7.8.4" + +"@babel/plugin-transform-async-to-generator@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz#c7a85f44e46f8952f6d27fe57c2ed3cc084c3775" + integrity sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ== + dependencies: + "@babel/helper-module-imports" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-remap-async-to-generator" "^7.22.5" + +"@babel/plugin-transform-async-to-generator@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz#d1f513c7a8a506d43f47df2bf25f9254b0b051fa" + integrity sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw== + dependencies: + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-remap-async-to-generator" "^7.22.20" + +"@babel/plugin-transform-block-scoped-functions@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz#27978075bfaeb9fa586d3cb63a3d30c1de580024" + integrity sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-block-scoped-functions@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz#fe1177d715fb569663095e04f3598525d98e8c77" + integrity sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-block-scoping@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.5.tgz#8bfc793b3a4b2742c0983fadc1480d843ecea31b" + integrity sha512-EcACl1i5fSQ6bt+YGuU/XGCeZKStLmyVGytWkpyhCLeQVA0eu6Wtiw92V+I1T/hnezUv7j74dA/Ro69gWcU+hg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-block-scoping@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz#b2d38589531c6c80fbe25e6b58e763622d2d3cf5" + integrity sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-class-properties@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz#97a56e31ad8c9dc06a0b3710ce7803d5a48cca77" + integrity sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-class-properties@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz#35c377db11ca92a785a718b6aa4e3ed1eb65dc48" + integrity sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-class-static-block@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz#3e40c46f048403472d6f4183116d5e46b1bff5ba" + integrity sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + +"@babel/plugin-transform-class-static-block@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz#2a202c8787a8964dd11dfcedf994d36bfc844ab5" + integrity sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + +"@babel/plugin-transform-classes@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.6.tgz#e04d7d804ed5b8501311293d1a0e6d43e94c3363" + integrity sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-compilation-targets" "^7.22.6" + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-function-name" "^7.22.5" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + globals "^11.1.0" + +"@babel/plugin-transform-classes@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.5.tgz#e7a75f815e0c534cc4c9a39c56636c84fc0d64f2" + integrity sha512-jvOTR4nicqYC9yzOHIhXG5emiFEOpappSJAl73SDSEDcybD+Puuze8Tnpb9p9qEyYup24tq891gkaygIFvWDqg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-compilation-targets" "^7.22.15" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.20" + "@babel/helper-split-export-declaration" "^7.22.6" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz#cd1e994bf9f316bd1c2dafcd02063ec261bb3869" + integrity sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/template" "^7.22.5" + +"@babel/plugin-transform-computed-properties@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz#652e69561fcc9d2b50ba4f7ac7f60dcf65e86474" + integrity sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/template" "^7.22.15" + +"@babel/plugin-transform-destructuring@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.5.tgz#d3aca7438f6c26c78cdd0b0ba920a336001b27cc" + integrity sha512-GfqcFuGW8vnEqTUBM7UtPd5A4q797LTvvwKxXTgRsFjoqaJiEg9deBG6kWeQYkVEL569NpnmpC0Pkr/8BLKGnQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-destructuring@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz#8c9ee68228b12ae3dff986e56ed1ba4f3c446311" + integrity sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-dotall-regex@^7.22.5", "@babel/plugin-transform-dotall-regex@^7.4.4": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz#dbb4f0e45766eb544e193fb00e65a1dd3b2a4165" + integrity sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-dotall-regex@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz#3f7af6054882ede89c378d0cf889b854a993da50" + integrity sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-duplicate-keys@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz#b6e6428d9416f5f0bba19c70d1e6e7e0b88ab285" + integrity sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-duplicate-keys@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz#664706ca0a5dfe8d066537f99032fc1dc8b720ce" + integrity sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-dynamic-import@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz#d6908a8916a810468c4edff73b5b75bda6ad393e" + integrity sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + +"@babel/plugin-transform-dynamic-import@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz#c7629e7254011ac3630d47d7f34ddd40ca535143" + integrity sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + +"@babel/plugin-transform-exponentiation-operator@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz#402432ad544a1f9a480da865fda26be653e48f6a" + integrity sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-exponentiation-operator@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz#ea0d978f6b9232ba4722f3dbecdd18f450babd18" + integrity sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-export-namespace-from@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz#57c41cb1d0613d22f548fddd8b288eedb9973a5b" + integrity sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + +"@babel/plugin-transform-export-namespace-from@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz#084c7b25e9a5c8271e987a08cf85807b80283191" + integrity sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + +"@babel/plugin-transform-for-of@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz#ab1b8a200a8f990137aff9a084f8de4099ab173f" + integrity sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-for-of@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz#81c37e24171b37b370ba6aaffa7ac86bcb46f94e" + integrity sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + +"@babel/plugin-transform-function-name@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz#935189af68b01898e0d6d99658db6b164205c143" + integrity sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg== + dependencies: + "@babel/helper-compilation-targets" "^7.22.5" + "@babel/helper-function-name" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-function-name@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz#8f424fcd862bf84cb9a1a6b42bc2f47ed630f8dc" + integrity sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw== + dependencies: + "@babel/helper-compilation-targets" "^7.22.15" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-json-strings@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz#14b64352fdf7e1f737eed68de1a1468bd2a77ec0" + integrity sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-json-strings" "^7.8.3" + +"@babel/plugin-transform-json-strings@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz#a871d9b6bd171976efad2e43e694c961ffa3714d" + integrity sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-json-strings" "^7.8.3" + +"@babel/plugin-transform-literals@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz#e9341f4b5a167952576e23db8d435849b1dd7920" + integrity sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-literals@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz#8214665f00506ead73de157eba233e7381f3beb4" + integrity sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-logical-assignment-operators@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz#66ae5f068fd5a9a5dc570df16f56c2a8462a9d6c" + integrity sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + +"@babel/plugin-transform-logical-assignment-operators@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz#e599f82c51d55fac725f62ce55d3a0886279ecb5" + integrity sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + +"@babel/plugin-transform-member-expression-literals@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz#4fcc9050eded981a468347dd374539ed3e058def" + integrity sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-member-expression-literals@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz#e37b3f0502289f477ac0e776b05a833d853cabcc" + integrity sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-modules-amd@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz#4e045f55dcf98afd00f85691a68fc0780704f526" + integrity sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ== + dependencies: + "@babel/helper-module-transforms" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-modules-amd@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz#e19b55436a1416829df0a1afc495deedfae17f7d" + integrity sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw== + dependencies: + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-modules-commonjs@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz#7d9875908d19b8c0536085af7b053fd5bd651bfa" + integrity sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA== + dependencies: + "@babel/helper-module-transforms" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-simple-access" "^7.22.5" + +"@babel/plugin-transform-modules-commonjs@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz#661ae831b9577e52be57dd8356b734f9700b53b4" + integrity sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA== + dependencies: + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-simple-access" "^7.22.5" + +"@babel/plugin-transform-modules-systemjs@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz#18c31410b5e579a0092638f95c896c2a98a5d496" + integrity sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ== + dependencies: + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-module-transforms" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.5" + +"@babel/plugin-transform-modules-systemjs@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.3.tgz#fa7e62248931cb15b9404f8052581c302dd9de81" + integrity sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ== + dependencies: + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.20" + +"@babel/plugin-transform-modules-umd@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz#4694ae40a87b1745e3775b6a7fe96400315d4f98" + integrity sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ== + dependencies: + "@babel/helper-module-transforms" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-modules-umd@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz#5d4395fccd071dfefe6585a4411aa7d6b7d769e9" + integrity sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg== + dependencies: + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz#67fe18ee8ce02d57c855185e27e3dc959b2e991f" + integrity sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-new-target@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz#1b248acea54ce44ea06dfd37247ba089fcf9758d" + integrity sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-new-target@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz#5491bb78ed6ac87e990957cea367eab781c4d980" + integrity sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-nullish-coalescing-operator@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz#f8872c65776e0b552e0849d7596cddd416c3e381" + integrity sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + +"@babel/plugin-transform-nullish-coalescing-operator@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz#45556aad123fc6e52189ea749e33ce090637346e" + integrity sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + +"@babel/plugin-transform-numeric-separator@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz#57226a2ed9e512b9b446517ab6fa2d17abb83f58" + integrity sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + +"@babel/plugin-transform-numeric-separator@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz#03d08e3691e405804ecdd19dd278a40cca531f29" + integrity sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + +"@babel/plugin-transform-object-rest-spread@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz#9686dc3447df4753b0b2a2fae7e8bc33cdc1f2e1" + integrity sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ== + dependencies: + "@babel/compat-data" "^7.22.5" + "@babel/helper-compilation-targets" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.22.5" + +"@babel/plugin-transform-object-rest-spread@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz#2b9c2d26bf62710460bdc0d1730d4f1048361b83" + integrity sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g== + dependencies: + "@babel/compat-data" "^7.23.3" + "@babel/helper-compilation-targets" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.23.3" + +"@babel/plugin-transform-object-super@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz#794a8d2fcb5d0835af722173c1a9d704f44e218c" + integrity sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.5" + +"@babel/plugin-transform-object-super@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz#81fdb636dcb306dd2e4e8fd80db5b2362ed2ebcd" + integrity sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.20" + +"@babel/plugin-transform-optional-catch-binding@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz#842080be3076703be0eaf32ead6ac8174edee333" + integrity sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + +"@babel/plugin-transform-optional-catch-binding@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz#318066de6dacce7d92fa244ae475aa8d91778017" + integrity sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + +"@babel/plugin-transform-optional-chaining@^7.22.5", "@babel/plugin-transform-optional-chaining@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.6.tgz#4bacfe37001fe1901117672875e931d439811564" + integrity sha512-Vd5HiWml0mDVtcLHIoEU5sw6HOUW/Zk0acLs/SAeuLzkGNOPc9DB4nkUajemhCmTIz3eiaKREZn2hQQqF79YTg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + +"@babel/plugin-transform-optional-chaining@^7.23.3", "@babel/plugin-transform-optional-chaining@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz#6acf61203bdfc4de9d4e52e64490aeb3e52bd017" + integrity sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + +"@babel/plugin-transform-parameters@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz#c3542dd3c39b42c8069936e48717a8d179d63a18" + integrity sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-parameters@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz#83ef5d1baf4b1072fa6e54b2b0999a7b2527e2af" + integrity sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-private-methods@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz#21c8af791f76674420a147ae62e9935d790f8722" + integrity sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-private-methods@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz#b2d7a3c97e278bfe59137a978d53b2c2e038c0e4" + integrity sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-private-property-in-object@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz#07a77f28cbb251546a43d175a1dda4cf3ef83e32" + integrity sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + +"@babel/plugin-transform-private-property-in-object@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz#3ec711d05d6608fd173d9b8de39872d8dbf68bf5" + integrity sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + +"@babel/plugin-transform-property-literals@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz#b5ddabd73a4f7f26cd0e20f5db48290b88732766" + integrity sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-property-literals@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz#54518f14ac4755d22b92162e4a852d308a560875" + integrity sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-react-constant-elements@^7.18.12": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.22.5.tgz#6dfa7c1c37f7d7279e417ceddf5a04abb8bb9c29" + integrity sha512-BF5SXoO+nX3h5OhlN78XbbDrBOffv+AxPP2ENaJOVqjWCgBDeOY3WcaUcddutGSfoap+5NEQ/q/4I3WZIvgkXA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-react-display-name@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.22.5.tgz#3c4326f9fce31c7968d6cb9debcaf32d9e279a2b" + integrity sha512-PVk3WPYudRF5z4GKMEYUrLjPl38fJSKNaEOkFuoprioowGuWN6w2RKznuFNSlJx7pzzXXStPUnNSOEO0jL5EVw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-react-display-name@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.23.3.tgz#70529f034dd1e561045ad3c8152a267f0d7b6200" + integrity sha512-GnvhtVfA2OAtzdX58FJxU19rhoGeQzyVndw3GgtdECQvQFXPEZIOVULHVZGAYmOgmqjXpVpfocAbSjh99V/Fqw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-react-jsx-development@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz#e716b6edbef972a92165cd69d92f1255f7e73e87" + integrity sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A== + dependencies: + "@babel/plugin-transform-react-jsx" "^7.22.5" + +"@babel/plugin-transform-react-jsx@^7.22.15": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz#393f99185110cea87184ea47bcb4a7b0c2e39312" + integrity sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-jsx" "^7.23.3" + "@babel/types" "^7.23.4" + +"@babel/plugin-transform-react-jsx@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.5.tgz#932c291eb6dd1153359e2a90cb5e557dcf068416" + integrity sha512-rog5gZaVbUip5iWDMTYbVM15XQq+RkUKhET/IHR6oizR+JEoN6CAfTTuHcK4vwUyzca30qqHqEpzBOnaRMWYMA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-module-imports" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-jsx" "^7.22.5" + "@babel/types" "^7.22.5" + +"@babel/plugin-transform-react-pure-annotations@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.22.5.tgz#1f58363eef6626d6fa517b95ac66fe94685e32c0" + integrity sha512-gP4k85wx09q+brArVinTXhWiyzLl9UpmGva0+mWyKxk6JZequ05x3eUcIUE+FyttPKJFRRVtAvQaJ6YF9h1ZpA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-react-pure-annotations@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.23.3.tgz#fabedbdb8ee40edf5da96f3ecfc6958e3783b93c" + integrity sha512-qMFdSS+TUhB7Q/3HVPnEdYJDQIk57jkntAwSuz9xfSE4n+3I+vHYCli3HoHawN1Z3RfCz/y1zXA/JXjG6cVImQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-regenerator@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.5.tgz#cd8a68b228a5f75fa01420e8cc2fc400f0fc32aa" + integrity sha512-rR7KePOE7gfEtNTh9Qw+iO3Q/e4DEsoQ+hdvM6QUDH7JRJ5qxq5AA52ZzBWbI5i9lfNuvySgOGP8ZN7LAmaiPw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + regenerator-transform "^0.15.1" + +"@babel/plugin-transform-regenerator@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz#141afd4a2057298602069fce7f2dc5173e6c561c" + integrity sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + regenerator-transform "^0.15.2" + +"@babel/plugin-transform-reserved-words@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz#832cd35b81c287c4bcd09ce03e22199641f964fb" + integrity sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-reserved-words@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz#4130dcee12bd3dd5705c587947eb715da12efac8" + integrity sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-runtime@^7.22.9": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.6.tgz#bf853cd0a675c16ee33e6ba2a63b536e75e5d754" + integrity sha512-kF1Zg62aPseQ11orDhFRw+aPG/eynNQtI+TyY+m33qJa2cJ5EEvza2P2BNTIA9E5MyqFABHEyY6CPHwgdy9aNg== + dependencies: + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + babel-plugin-polyfill-corejs2 "^0.4.6" + babel-plugin-polyfill-corejs3 "^0.8.5" + babel-plugin-polyfill-regenerator "^0.5.3" + semver "^6.3.1" + +"@babel/plugin-transform-shorthand-properties@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz#6e277654be82b5559fc4b9f58088507c24f0c624" + integrity sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-shorthand-properties@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz#97d82a39b0e0c24f8a981568a8ed851745f59210" + integrity sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-spread@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz#6487fd29f229c95e284ba6c98d65eafb893fea6b" + integrity sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + +"@babel/plugin-transform-spread@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz#41d17aacb12bde55168403c6f2d6bdca563d362c" + integrity sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + +"@babel/plugin-transform-sticky-regex@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz#295aba1595bfc8197abd02eae5fc288c0deb26aa" + integrity sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-sticky-regex@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz#dec45588ab4a723cb579c609b294a3d1bd22ff04" + integrity sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-template-literals@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz#8f38cf291e5f7a8e60e9f733193f0bcc10909bff" + integrity sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-template-literals@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz#5f0f028eb14e50b5d0f76be57f90045757539d07" + integrity sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-typeof-symbol@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz#5e2ba478da4b603af8673ff7c54f75a97b716b34" + integrity sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-typeof-symbol@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz#9dfab97acc87495c0c449014eb9c547d8966bca4" + integrity sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-typescript@^7.22.5": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.9.tgz#91e08ad1eb1028ecc62662a842e93ecfbf3c7234" + integrity sha512-BnVR1CpKiuD0iobHPaM1iLvcwPYN2uVFAqoLVSpEDKWuOikoCv5HbKLxclhKYUXlWkX86DoZGtqI4XhbOsyrMg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.22.9" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-typescript" "^7.22.5" + +"@babel/plugin-transform-typescript@^7.23.3": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.6.tgz#aa36a94e5da8d94339ae3a4e22d40ed287feb34c" + integrity sha512-6cBG5mBvUu4VUD04OHKnYzbuHNP8huDsD3EDqqpIpsswTDoqHCjLoHb6+QgsV1WsT2nipRqCPgxD3LXnEO7XfA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.23.6" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-typescript" "^7.23.3" + +"@babel/plugin-transform-unicode-escapes@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.5.tgz#ce0c248522b1cb22c7c992d88301a5ead70e806c" + integrity sha512-biEmVg1IYB/raUO5wT1tgfacCef15Fbzhkx493D3urBI++6hpJ+RFG4SrWMn0NEZLfvilqKf3QDrRVZHo08FYg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-unicode-escapes@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz#1f66d16cab01fab98d784867d24f70c1ca65b925" + integrity sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-unicode-property-regex@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz#098898f74d5c1e86660dc112057b2d11227f1c81" + integrity sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-unicode-property-regex@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz#19e234129e5ffa7205010feec0d94c251083d7ad" + integrity sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-unicode-regex@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz#ce7e7bb3ef208c4ff67e02a22816656256d7a183" + integrity sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-unicode-regex@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz#26897708d8f42654ca4ce1b73e96140fbad879dc" + integrity sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-unicode-sets-regex@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz#77788060e511b708ffc7d42fdfbc5b37c3004e91" + integrity sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-unicode-sets-regex@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz#4fb6f0a719c2c5859d11f6b55a050cc987f3799e" + integrity sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/preset-env@^7.19.4": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.9.tgz#57f17108eb5dfd4c5c25a44c1977eba1df310ac7" + integrity sha512-wNi5H/Emkhll/bqPjsjQorSykrlfY5OWakd6AulLvMEytpKasMVUpVy8RL4qBIBs5Ac6/5i0/Rv0b/Fg6Eag/g== + dependencies: + "@babel/compat-data" "^7.22.9" + "@babel/helper-compilation-targets" "^7.22.9" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-validator-option" "^7.22.5" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.22.5" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.22.5" + "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-import-assertions" "^7.22.5" + "@babel/plugin-syntax-import-attributes" "^7.22.5" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" + "@babel/plugin-transform-arrow-functions" "^7.22.5" + "@babel/plugin-transform-async-generator-functions" "^7.22.7" + "@babel/plugin-transform-async-to-generator" "^7.22.5" + "@babel/plugin-transform-block-scoped-functions" "^7.22.5" + "@babel/plugin-transform-block-scoping" "^7.22.5" + "@babel/plugin-transform-class-properties" "^7.22.5" + "@babel/plugin-transform-class-static-block" "^7.22.5" + "@babel/plugin-transform-classes" "^7.22.6" + "@babel/plugin-transform-computed-properties" "^7.22.5" + "@babel/plugin-transform-destructuring" "^7.22.5" + "@babel/plugin-transform-dotall-regex" "^7.22.5" + "@babel/plugin-transform-duplicate-keys" "^7.22.5" + "@babel/plugin-transform-dynamic-import" "^7.22.5" + "@babel/plugin-transform-exponentiation-operator" "^7.22.5" + "@babel/plugin-transform-export-namespace-from" "^7.22.5" + "@babel/plugin-transform-for-of" "^7.22.5" + "@babel/plugin-transform-function-name" "^7.22.5" + "@babel/plugin-transform-json-strings" "^7.22.5" + "@babel/plugin-transform-literals" "^7.22.5" + "@babel/plugin-transform-logical-assignment-operators" "^7.22.5" + "@babel/plugin-transform-member-expression-literals" "^7.22.5" + "@babel/plugin-transform-modules-amd" "^7.22.5" + "@babel/plugin-transform-modules-commonjs" "^7.22.5" + "@babel/plugin-transform-modules-systemjs" "^7.22.5" + "@babel/plugin-transform-modules-umd" "^7.22.5" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.5" + "@babel/plugin-transform-new-target" "^7.22.5" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.22.5" + "@babel/plugin-transform-numeric-separator" "^7.22.5" + "@babel/plugin-transform-object-rest-spread" "^7.22.5" + "@babel/plugin-transform-object-super" "^7.22.5" + "@babel/plugin-transform-optional-catch-binding" "^7.22.5" + "@babel/plugin-transform-optional-chaining" "^7.22.6" + "@babel/plugin-transform-parameters" "^7.22.5" + "@babel/plugin-transform-private-methods" "^7.22.5" + "@babel/plugin-transform-private-property-in-object" "^7.22.5" + "@babel/plugin-transform-property-literals" "^7.22.5" + "@babel/plugin-transform-regenerator" "^7.22.5" + "@babel/plugin-transform-reserved-words" "^7.22.5" + "@babel/plugin-transform-shorthand-properties" "^7.22.5" + "@babel/plugin-transform-spread" "^7.22.5" + "@babel/plugin-transform-sticky-regex" "^7.22.5" + "@babel/plugin-transform-template-literals" "^7.22.5" + "@babel/plugin-transform-typeof-symbol" "^7.22.5" + "@babel/plugin-transform-unicode-escapes" "^7.22.5" + "@babel/plugin-transform-unicode-property-regex" "^7.22.5" + "@babel/plugin-transform-unicode-regex" "^7.22.5" + "@babel/plugin-transform-unicode-sets-regex" "^7.22.5" + "@babel/preset-modules" "^0.1.5" + "@babel/types" "^7.22.5" + babel-plugin-polyfill-corejs2 "^0.4.4" + babel-plugin-polyfill-corejs3 "^0.8.2" + babel-plugin-polyfill-regenerator "^0.5.1" + core-js-compat "^3.31.0" + semver "^6.3.1" + +"@babel/preset-env@^7.22.9": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.23.6.tgz#ad0ea799d5a3c07db5b9a172819bbd444092187a" + integrity sha512-2XPn/BqKkZCpzYhUUNZ1ssXw7DcXfKQEjv/uXZUXgaebCMYmkEsfZ2yY+vv+xtXv50WmL5SGhyB6/xsWxIvvOQ== + dependencies: + "@babel/compat-data" "^7.23.5" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-validator-option" "^7.23.5" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.23.3" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.23.3" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.23.3" + "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-import-assertions" "^7.23.3" + "@babel/plugin-syntax-import-attributes" "^7.23.3" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" + "@babel/plugin-transform-arrow-functions" "^7.23.3" + "@babel/plugin-transform-async-generator-functions" "^7.23.4" + "@babel/plugin-transform-async-to-generator" "^7.23.3" + "@babel/plugin-transform-block-scoped-functions" "^7.23.3" + "@babel/plugin-transform-block-scoping" "^7.23.4" + "@babel/plugin-transform-class-properties" "^7.23.3" + "@babel/plugin-transform-class-static-block" "^7.23.4" + "@babel/plugin-transform-classes" "^7.23.5" + "@babel/plugin-transform-computed-properties" "^7.23.3" + "@babel/plugin-transform-destructuring" "^7.23.3" + "@babel/plugin-transform-dotall-regex" "^7.23.3" + "@babel/plugin-transform-duplicate-keys" "^7.23.3" + "@babel/plugin-transform-dynamic-import" "^7.23.4" + "@babel/plugin-transform-exponentiation-operator" "^7.23.3" + "@babel/plugin-transform-export-namespace-from" "^7.23.4" + "@babel/plugin-transform-for-of" "^7.23.6" + "@babel/plugin-transform-function-name" "^7.23.3" + "@babel/plugin-transform-json-strings" "^7.23.4" + "@babel/plugin-transform-literals" "^7.23.3" + "@babel/plugin-transform-logical-assignment-operators" "^7.23.4" + "@babel/plugin-transform-member-expression-literals" "^7.23.3" + "@babel/plugin-transform-modules-amd" "^7.23.3" + "@babel/plugin-transform-modules-commonjs" "^7.23.3" + "@babel/plugin-transform-modules-systemjs" "^7.23.3" + "@babel/plugin-transform-modules-umd" "^7.23.3" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.5" + "@babel/plugin-transform-new-target" "^7.23.3" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.23.4" + "@babel/plugin-transform-numeric-separator" "^7.23.4" + "@babel/plugin-transform-object-rest-spread" "^7.23.4" + "@babel/plugin-transform-object-super" "^7.23.3" + "@babel/plugin-transform-optional-catch-binding" "^7.23.4" + "@babel/plugin-transform-optional-chaining" "^7.23.4" + "@babel/plugin-transform-parameters" "^7.23.3" + "@babel/plugin-transform-private-methods" "^7.23.3" + "@babel/plugin-transform-private-property-in-object" "^7.23.4" + "@babel/plugin-transform-property-literals" "^7.23.3" + "@babel/plugin-transform-regenerator" "^7.23.3" + "@babel/plugin-transform-reserved-words" "^7.23.3" + "@babel/plugin-transform-shorthand-properties" "^7.23.3" + "@babel/plugin-transform-spread" "^7.23.3" + "@babel/plugin-transform-sticky-regex" "^7.23.3" + "@babel/plugin-transform-template-literals" "^7.23.3" + "@babel/plugin-transform-typeof-symbol" "^7.23.3" + "@babel/plugin-transform-unicode-escapes" "^7.23.3" + "@babel/plugin-transform-unicode-property-regex" "^7.23.3" + "@babel/plugin-transform-unicode-regex" "^7.23.3" + "@babel/plugin-transform-unicode-sets-regex" "^7.23.3" + "@babel/preset-modules" "0.1.6-no-external-plugins" + babel-plugin-polyfill-corejs2 "^0.4.6" + babel-plugin-polyfill-corejs3 "^0.8.5" + babel-plugin-polyfill-regenerator "^0.5.3" + core-js-compat "^3.31.0" + semver "^6.3.1" + +"@babel/preset-modules@0.1.6-no-external-plugins": + version "0.1.6-no-external-plugins" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz#ccb88a2c49c817236861fee7826080573b8a923a" + integrity sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + +"@babel/preset-modules@^0.1.5": + version "0.1.6" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.6.tgz#31bcdd8f19538437339d17af00d177d854d9d458" + integrity sha512-ID2yj6K/4lKfhuU3+EX4UvNbIt7eACFbHmNUjzA+ep+B5971CknnA/9DEWKbRokfbbtblxxxXFJJrH47UEAMVg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" + "@babel/plugin-transform-dotall-regex" "^7.4.4" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + +"@babel/preset-react@^7.18.6": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.22.5.tgz#c4d6058fbf80bccad02dd8c313a9aaa67e3c3dd6" + integrity sha512-M+Is3WikOpEJHgR385HbuCITPTaPRaNkibTEa9oiofmJvIsrceb4yp9RL9Kb+TE8LznmeyZqpP+Lopwcx59xPQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-validator-option" "^7.22.5" + "@babel/plugin-transform-react-display-name" "^7.22.5" + "@babel/plugin-transform-react-jsx" "^7.22.5" + "@babel/plugin-transform-react-jsx-development" "^7.22.5" + "@babel/plugin-transform-react-pure-annotations" "^7.22.5" + +"@babel/preset-react@^7.22.5": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.23.3.tgz#f73ca07e7590f977db07eb54dbe46538cc015709" + integrity sha512-tbkHOS9axH6Ysf2OUEqoSZ6T3Fa2SrNH6WTWSPBboxKzdxNc9qOICeLXkNG0ZEwbQ1HY8liwOce4aN/Ceyuq6w== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-validator-option" "^7.22.15" + "@babel/plugin-transform-react-display-name" "^7.23.3" + "@babel/plugin-transform-react-jsx" "^7.22.15" + "@babel/plugin-transform-react-jsx-development" "^7.22.5" + "@babel/plugin-transform-react-pure-annotations" "^7.23.3" + +"@babel/preset-typescript@^7.18.6": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.22.5.tgz#16367d8b01d640e9a507577ed4ee54e0101e51c8" + integrity sha512-YbPaal9LxztSGhmndR46FmAbkJ/1fAsw293tSU+I5E5h+cnJ3d4GTwyUgGYmOXJYdGA+uNePle4qbaRzj2NISQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-validator-option" "^7.22.5" + "@babel/plugin-syntax-jsx" "^7.22.5" + "@babel/plugin-transform-modules-commonjs" "^7.22.5" + "@babel/plugin-transform-typescript" "^7.22.5" + +"@babel/preset-typescript@^7.22.5": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.23.3.tgz#14534b34ed5b6d435aa05f1ae1c5e7adcc01d913" + integrity sha512-17oIGVlqz6CchO9RFYn5U6ZpWRZIngayYCtrPRSgANSwC2V1Jb+iP74nVxzzXJte8b8BYxrL1yY96xfhTBrNNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-validator-option" "^7.22.15" + "@babel/plugin-syntax-jsx" "^7.23.3" + "@babel/plugin-transform-modules-commonjs" "^7.23.3" + "@babel/plugin-transform-typescript" "^7.23.3" + +"@babel/regjsgen@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" + integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== + +"@babel/runtime-corejs3@^7.22.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.23.6.tgz#c25dd662fc205a03fdaefd122066eb9d4533ccf9" + integrity sha512-Djs/ZTAnpyj0nyg7p1J6oiE/tZ9G2stqAFlLGZynrW+F3k2w2jGK2mLOBxzYIOcZYA89+c3d3wXKpYLcpwcU6w== + dependencies: + core-js-pure "^3.30.2" + regenerator-runtime "^0.14.0" + +"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.8.4": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.6.tgz#57d64b9ae3cff1d67eb067ae117dac087f5bd438" + integrity sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ== + dependencies: + regenerator-runtime "^0.13.11" + +"@babel/runtime@^7.22.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.6.tgz#c05e610dc228855dc92ef1b53d07389ed8ab521d" + integrity sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ== + dependencies: + regenerator-runtime "^0.14.0" + +"@babel/template@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" + integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== + dependencies: + "@babel/code-frame" "^7.22.13" + "@babel/parser" "^7.22.15" + "@babel/types" "^7.22.15" + +"@babel/template@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec" + integrity sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw== + dependencies: + "@babel/code-frame" "^7.22.5" + "@babel/parser" "^7.22.5" + "@babel/types" "^7.22.5" + +"@babel/traverse@^7.22.6", "@babel/traverse@^7.22.8": + version "7.22.8" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.8.tgz#4d4451d31bc34efeae01eac222b514a77aa4000e" + integrity sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw== + dependencies: + "@babel/code-frame" "^7.22.5" + "@babel/generator" "^7.22.7" + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-function-name" "^7.22.5" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.22.7" + "@babel/types" "^7.22.5" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/traverse@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.6.tgz#b53526a2367a0dd6edc423637f3d2d0f2521abc5" + integrity sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.23.6" + "@babel/types" "^7.23.6" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.20.0", "@babel/types@^7.22.5", "@babel/types@^7.4.4": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.5.tgz#cd93eeaab025880a3a47ec881f4b096a5b786fbe" + integrity sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA== + dependencies: + "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.5" + to-fast-properties "^2.0.0" + +"@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.23.0", "@babel/types@^7.23.4", "@babel/types@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.6.tgz#be33fdb151e1f5a56877d704492c240fc71c7ccd" + integrity sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg== + dependencies: + "@babel/helper-string-parser" "^7.23.4" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + +"@colors/colors@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" + integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== + +"@discoveryjs/json-ext@0.5.7": + version "0.5.7" + resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" + integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== + +"@docsearch/css@3.5.2": + version "3.5.2" + resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.5.2.tgz#610f47b48814ca94041df969d9fcc47b91fc5aac" + integrity sha512-SPiDHaWKQZpwR2siD0KQUwlStvIAnEyK6tAE2h2Wuoq8ue9skzhlyVQ1ddzOxX6khULnAALDiR/isSF3bnuciA== + +"@docsearch/react@^3.5.2": + version "3.5.2" + resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-3.5.2.tgz#2e6bbee00eb67333b64906352734da6aef1232b9" + integrity sha512-9Ahcrs5z2jq/DcAvYtvlqEBHImbm4YJI8M9y0x6Tqg598P40HTEkX7hsMcIuThI+hTFxRGZ9hll0Wygm2yEjng== + dependencies: + "@algolia/autocomplete-core" "1.9.3" + "@algolia/autocomplete-preset-algolia" "1.9.3" + "@docsearch/css" "3.5.2" + algoliasearch "^4.19.1" + +"@docusaurus/core@3.0.1", "@docusaurus/core@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-3.0.1.tgz#ad9a66b20802ea81b25e65db75d4ca952eda7e01" + integrity sha512-CXrLpOnW+dJdSv8M5FAJ3JBwXtL6mhUWxFA8aS0ozK6jBG/wgxERk5uvH28fCeFxOGbAT9v1e9dOMo1X2IEVhQ== + dependencies: + "@babel/core" "^7.23.3" + "@babel/generator" "^7.23.3" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-transform-runtime" "^7.22.9" + "@babel/preset-env" "^7.22.9" + "@babel/preset-react" "^7.22.5" + "@babel/preset-typescript" "^7.22.5" + "@babel/runtime" "^7.22.6" + "@babel/runtime-corejs3" "^7.22.6" + "@babel/traverse" "^7.22.8" + "@docusaurus/cssnano-preset" "3.0.1" + "@docusaurus/logger" "3.0.1" + "@docusaurus/mdx-loader" "3.0.1" + "@docusaurus/react-loadable" "5.5.2" + "@docusaurus/utils" "3.0.1" + "@docusaurus/utils-common" "3.0.1" + "@docusaurus/utils-validation" "3.0.1" + "@slorber/static-site-generator-webpack-plugin" "^4.0.7" + "@svgr/webpack" "^6.5.1" + autoprefixer "^10.4.14" + babel-loader "^9.1.3" + babel-plugin-dynamic-import-node "^2.3.3" + boxen "^6.2.1" + chalk "^4.1.2" + chokidar "^3.5.3" + clean-css "^5.3.2" + cli-table3 "^0.6.3" + combine-promises "^1.1.0" + commander "^5.1.0" + copy-webpack-plugin "^11.0.0" + core-js "^3.31.1" + css-loader "^6.8.1" + css-minimizer-webpack-plugin "^4.2.2" + cssnano "^5.1.15" + del "^6.1.1" + detect-port "^1.5.1" + escape-html "^1.0.3" + eta "^2.2.0" + file-loader "^6.2.0" + fs-extra "^11.1.1" + html-minifier-terser "^7.2.0" + html-tags "^3.3.1" + html-webpack-plugin "^5.5.3" + leven "^3.1.0" + lodash "^4.17.21" + mini-css-extract-plugin "^2.7.6" + postcss "^8.4.26" + postcss-loader "^7.3.3" + prompts "^2.4.2" + react-dev-utils "^12.0.1" + react-helmet-async "^1.3.0" + react-loadable "npm:@docusaurus/react-loadable@5.5.2" + react-loadable-ssr-addon-v5-slorber "^1.0.1" + react-router "^5.3.4" + react-router-config "^5.1.1" + react-router-dom "^5.3.4" + rtl-detect "^1.0.4" + semver "^7.5.4" + serve-handler "^6.1.5" + shelljs "^0.8.5" + terser-webpack-plugin "^5.3.9" + tslib "^2.6.0" + update-notifier "^6.0.2" + url-loader "^4.1.1" + webpack "^5.88.1" + webpack-bundle-analyzer "^4.9.0" + webpack-dev-server "^4.15.1" + webpack-merge "^5.9.0" + webpackbar "^5.0.2" + +"@docusaurus/cssnano-preset@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-3.0.1.tgz#22fbf2e97389e338747864baf011743846e8fd26" + integrity sha512-wjuXzkHMW+ig4BD6Ya1Yevx9UJadO4smNZCEljqBoQfIQrQskTswBs7lZ8InHP7mCt273a/y/rm36EZhqJhknQ== + dependencies: + cssnano-preset-advanced "^5.3.10" + postcss "^8.4.26" + postcss-sort-media-queries "^4.4.1" + tslib "^2.6.0" + +"@docusaurus/logger@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-3.0.1.tgz#06f512eef6c6ae4e2da63064257e01b1cdc41a82" + integrity sha512-I5L6Nk8OJzkVA91O2uftmo71LBSxe1vmOn9AMR6JRCzYeEBrqneWMH02AqMvjJ2NpMiviO+t0CyPjyYV7nxCWQ== + dependencies: + chalk "^4.1.2" + tslib "^2.6.0" + +"@docusaurus/lqip-loader@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@docusaurus/lqip-loader/-/lqip-loader-3.0.1.tgz#16cd73b1367f5dd0078dad9284f75614d026cbeb" + integrity sha512-hFSu8ltYo0ZnWBWmjMhSprOr6nNKG01YdMDxH/hahBfyaNDCkZU4o7mQNgUW845lvYdp6bhjyW31WJwBjOnLqw== + dependencies: + "@docusaurus/logger" "3.0.1" + file-loader "^6.2.0" + lodash "^4.17.21" + sharp "^0.32.3" + tslib "^2.6.0" + +"@docusaurus/mdx-loader@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-3.0.1.tgz#89f221e5bcc570983fd61d7ab56d6fbe36810b59" + integrity sha512-ldnTmvnvlrONUq45oKESrpy+lXtbnTcTsFkOTIDswe5xx5iWJjt6eSa0f99ZaWlnm24mlojcIGoUWNCS53qVlQ== + dependencies: + "@babel/parser" "^7.22.7" + "@babel/traverse" "^7.22.8" + "@docusaurus/logger" "3.0.1" + "@docusaurus/utils" "3.0.1" + "@docusaurus/utils-validation" "3.0.1" + "@mdx-js/mdx" "^3.0.0" + "@slorber/remark-comment" "^1.0.0" + escape-html "^1.0.3" + estree-util-value-to-estree "^3.0.1" + file-loader "^6.2.0" + fs-extra "^11.1.1" + image-size "^1.0.2" + mdast-util-mdx "^3.0.0" + mdast-util-to-string "^4.0.0" + rehype-raw "^7.0.0" + remark-directive "^3.0.0" + remark-emoji "^4.0.0" + remark-frontmatter "^5.0.0" + remark-gfm "^4.0.0" + stringify-object "^3.3.0" + tslib "^2.6.0" + unified "^11.0.3" + unist-util-visit "^5.0.0" + url-loader "^4.1.1" + vfile "^6.0.1" + webpack "^5.88.1" + +"@docusaurus/module-type-aliases@3.0.1", "@docusaurus/module-type-aliases@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-3.0.1.tgz#d45990fe377d7ffaa68841cf89401188a5d65293" + integrity sha512-DEHpeqUDsLynl3AhQQiO7AbC7/z/lBra34jTcdYuvp9eGm01pfH1wTVq8YqWZq6Jyx0BgcVl/VJqtE9StRd9Ag== + dependencies: + "@docusaurus/react-loadable" "5.5.2" + "@docusaurus/types" "3.0.1" + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-router-config" "*" + "@types/react-router-dom" "*" + react-helmet-async "*" + react-loadable "npm:@docusaurus/react-loadable@5.5.2" + +"@docusaurus/plugin-content-blog@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.0.1.tgz#dee6147187c2d8b634252444d60312d12c9571a6" + integrity sha512-cLOvtvAyaMQFLI8vm4j26svg3ktxMPSXpuUJ7EERKoGbfpJSsgtowNHcRsaBVmfuCsRSk1HZ/yHBsUkTmHFEsg== + dependencies: + "@docusaurus/core" "3.0.1" + "@docusaurus/logger" "3.0.1" + "@docusaurus/mdx-loader" "3.0.1" + "@docusaurus/types" "3.0.1" + "@docusaurus/utils" "3.0.1" + "@docusaurus/utils-common" "3.0.1" + "@docusaurus/utils-validation" "3.0.1" + cheerio "^1.0.0-rc.12" + feed "^4.2.2" + fs-extra "^11.1.1" + lodash "^4.17.21" + reading-time "^1.5.0" + srcset "^4.0.0" + tslib "^2.6.0" + unist-util-visit "^5.0.0" + utility-types "^3.10.0" + webpack "^5.88.1" + +"@docusaurus/plugin-content-docs@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.0.1.tgz#d9b1884562186573d5c4521ac3546b68512c1126" + integrity sha512-dRfAOA5Ivo+sdzzJGXEu33yAtvGg8dlZkvt/NEJ7nwi1F2j4LEdsxtfX2GKeETB2fP6XoGNSQnFXqa2NYGrHFg== + dependencies: + "@docusaurus/core" "3.0.1" + "@docusaurus/logger" "3.0.1" + "@docusaurus/mdx-loader" "3.0.1" + "@docusaurus/module-type-aliases" "3.0.1" + "@docusaurus/types" "3.0.1" + "@docusaurus/utils" "3.0.1" + "@docusaurus/utils-validation" "3.0.1" + "@types/react-router-config" "^5.0.7" + combine-promises "^1.1.0" + fs-extra "^11.1.1" + js-yaml "^4.1.0" + lodash "^4.17.21" + tslib "^2.6.0" + utility-types "^3.10.0" + webpack "^5.88.1" + +"@docusaurus/plugin-content-pages@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.0.1.tgz#27e6424c77173f867760efe53f848bbab8849ea6" + integrity sha512-oP7PoYizKAXyEttcvVzfX3OoBIXEmXTMzCdfmC4oSwjG4SPcJsRge3mmI6O8jcZBgUPjIzXD21bVGWEE1iu8gg== + dependencies: + "@docusaurus/core" "3.0.1" + "@docusaurus/mdx-loader" "3.0.1" + "@docusaurus/types" "3.0.1" + "@docusaurus/utils" "3.0.1" + "@docusaurus/utils-validation" "3.0.1" + fs-extra "^11.1.1" + tslib "^2.6.0" + webpack "^5.88.1" + +"@docusaurus/plugin-debug@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-3.0.1.tgz#886b5dd03c066e970484ca251c1b79613df90700" + integrity sha512-09dxZMdATky4qdsZGzhzlUvvC+ilQ2hKbYF+wez+cM2mGo4qHbv8+qKXqxq0CQZyimwlAOWQLoSozIXU0g0i7g== + dependencies: + "@docusaurus/core" "3.0.1" + "@docusaurus/types" "3.0.1" + "@docusaurus/utils" "3.0.1" + fs-extra "^11.1.1" + react-json-view-lite "^1.2.0" + tslib "^2.6.0" + +"@docusaurus/plugin-google-analytics@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.0.1.tgz#ec69902131ea3aad8b062eeb1d17bf0962986f80" + integrity sha512-jwseSz1E+g9rXQwDdr0ZdYNjn8leZBnKPjjQhMBEiwDoenL3JYFcNW0+p0sWoVF/f2z5t7HkKA+cYObrUh18gg== + dependencies: + "@docusaurus/core" "3.0.1" + "@docusaurus/types" "3.0.1" + "@docusaurus/utils-validation" "3.0.1" + tslib "^2.6.0" + +"@docusaurus/plugin-google-gtag@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.0.1.tgz#bb5526377d3a324ebec235127846fda386562b05" + integrity sha512-UFTDvXniAWrajsulKUJ1DB6qplui1BlKLQZjX4F7qS/qfJ+qkKqSkhJ/F4VuGQ2JYeZstYb+KaUzUzvaPK1aRQ== + dependencies: + "@docusaurus/core" "3.0.1" + "@docusaurus/types" "3.0.1" + "@docusaurus/utils-validation" "3.0.1" + "@types/gtag.js" "^0.0.12" + tslib "^2.6.0" + +"@docusaurus/plugin-google-tag-manager@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.0.1.tgz#4e36d13279cf90c2614b62438aa1109dd4696ec8" + integrity sha512-IPFvuz83aFuheZcWpTlAdiiX1RqWIHM+OH8wS66JgwAKOiQMR3+nLywGjkLV4bp52x7nCnwhNk1rE85Cpy/CIw== + dependencies: + "@docusaurus/core" "3.0.1" + "@docusaurus/types" "3.0.1" + "@docusaurus/utils-validation" "3.0.1" + tslib "^2.6.0" + +"@docusaurus/plugin-ideal-image@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-ideal-image/-/plugin-ideal-image-3.0.1.tgz#fa0d947783fa8be774c6f912e8cf604ad1023689" + integrity sha512-IvAUpEIz6v1/fVz6UTdQY12pYIE5geNFtsuKpsULpMaotwYf3Gs7acXjQog4qquKkc65yV5zuvMj8BZMHEwLyQ== + dependencies: + "@docusaurus/core" "3.0.1" + "@docusaurus/lqip-loader" "3.0.1" + "@docusaurus/responsive-loader" "^1.7.0" + "@docusaurus/theme-translations" "3.0.1" + "@docusaurus/types" "3.0.1" + "@docusaurus/utils-validation" "3.0.1" + "@slorber/react-ideal-image" "^0.0.12" + react-waypoint "^10.3.0" + sharp "^0.32.3" + tslib "^2.6.0" + webpack "^5.88.1" + +"@docusaurus/plugin-sitemap@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.0.1.tgz#ab55857e90d4500f892e110b30e4bc3289202bd4" + integrity sha512-xARiWnjtVvoEniZudlCq5T9ifnhCu/GAZ5nA7XgyLfPcNpHQa241HZdsTlLtVcecEVVdllevBKOp7qknBBaMGw== + dependencies: + "@docusaurus/core" "3.0.1" + "@docusaurus/logger" "3.0.1" + "@docusaurus/types" "3.0.1" + "@docusaurus/utils" "3.0.1" + "@docusaurus/utils-common" "3.0.1" + "@docusaurus/utils-validation" "3.0.1" + fs-extra "^11.1.1" + sitemap "^7.1.1" + tslib "^2.6.0" + +"@docusaurus/preset-classic@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-3.0.1.tgz#d363ac837bba967095ed2a896d13c54f3717d6b5" + integrity sha512-il9m9xZKKjoXn6h0cRcdnt6wce0Pv1y5t4xk2Wx7zBGhKG1idu4IFHtikHlD0QPuZ9fizpXspXcTzjL5FXc1Gw== + dependencies: + "@docusaurus/core" "3.0.1" + "@docusaurus/plugin-content-blog" "3.0.1" + "@docusaurus/plugin-content-docs" "3.0.1" + "@docusaurus/plugin-content-pages" "3.0.1" + "@docusaurus/plugin-debug" "3.0.1" + "@docusaurus/plugin-google-analytics" "3.0.1" + "@docusaurus/plugin-google-gtag" "3.0.1" + "@docusaurus/plugin-google-tag-manager" "3.0.1" + "@docusaurus/plugin-sitemap" "3.0.1" + "@docusaurus/theme-classic" "3.0.1" + "@docusaurus/theme-common" "3.0.1" + "@docusaurus/theme-search-algolia" "3.0.1" + "@docusaurus/types" "3.0.1" + +"@docusaurus/react-loadable@5.5.2", "react-loadable@npm:@docusaurus/react-loadable@5.5.2": + version "5.5.2" + resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce" + integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ== + dependencies: + "@types/react" "*" + prop-types "^15.6.2" + +"@docusaurus/responsive-loader@^1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@docusaurus/responsive-loader/-/responsive-loader-1.7.0.tgz#508df2779e04311aa2a38efb67cf743109afd681" + integrity sha512-N0cWuVqTRXRvkBxeMQcy/OF2l7GN8rmni5EzR3HpwR+iU2ckYPnziceojcxvvxQ5NqZg1QfEW0tycQgHp+e+Nw== + dependencies: + loader-utils "^2.0.0" + +"@docusaurus/theme-classic@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-3.0.1.tgz#3ba4dc77553d2c1608e433c0d01bed7c6db14eb9" + integrity sha512-XD1FRXaJiDlmYaiHHdm27PNhhPboUah9rqIH0lMpBt5kYtsGjJzhqa27KuZvHLzOP2OEpqd2+GZ5b6YPq7Q05Q== + dependencies: + "@docusaurus/core" "3.0.1" + "@docusaurus/mdx-loader" "3.0.1" + "@docusaurus/module-type-aliases" "3.0.1" + "@docusaurus/plugin-content-blog" "3.0.1" + "@docusaurus/plugin-content-docs" "3.0.1" + "@docusaurus/plugin-content-pages" "3.0.1" + "@docusaurus/theme-common" "3.0.1" + "@docusaurus/theme-translations" "3.0.1" + "@docusaurus/types" "3.0.1" + "@docusaurus/utils" "3.0.1" + "@docusaurus/utils-common" "3.0.1" + "@docusaurus/utils-validation" "3.0.1" + "@mdx-js/react" "^3.0.0" + clsx "^2.0.0" + copy-text-to-clipboard "^3.2.0" + infima "0.2.0-alpha.43" + lodash "^4.17.21" + nprogress "^0.2.0" + postcss "^8.4.26" + prism-react-renderer "^2.3.0" + prismjs "^1.29.0" + react-router-dom "^5.3.4" + rtlcss "^4.1.0" + tslib "^2.6.0" + utility-types "^3.10.0" + +"@docusaurus/theme-common@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-3.0.1.tgz#29a5bcb286296a52bc10afa5308e360cbed6b49c" + integrity sha512-cr9TOWXuIOL0PUfuXv6L5lPlTgaphKP+22NdVBOYah5jSq5XAAulJTjfe+IfLsEG4L7lJttLbhW7LXDFSAI7Ag== + dependencies: + "@docusaurus/mdx-loader" "3.0.1" + "@docusaurus/module-type-aliases" "3.0.1" + "@docusaurus/plugin-content-blog" "3.0.1" + "@docusaurus/plugin-content-docs" "3.0.1" + "@docusaurus/plugin-content-pages" "3.0.1" + "@docusaurus/utils" "3.0.1" + "@docusaurus/utils-common" "3.0.1" + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-router-config" "*" + clsx "^2.0.0" + parse-numeric-range "^1.3.0" + prism-react-renderer "^2.3.0" + tslib "^2.6.0" + utility-types "^3.10.0" + +"@docusaurus/theme-search-algolia@3.0.1", "@docusaurus/theme-search-algolia@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.0.1.tgz#d8fb6bddca8d8355e4706c4c7d30d3b800217cf4" + integrity sha512-DDiPc0/xmKSEdwFkXNf1/vH1SzJPzuJBar8kMcBbDAZk/SAmo/4lf6GU2drou4Ae60lN2waix+jYWTWcJRahSA== + dependencies: + "@docsearch/react" "^3.5.2" + "@docusaurus/core" "3.0.1" + "@docusaurus/logger" "3.0.1" + "@docusaurus/plugin-content-docs" "3.0.1" + "@docusaurus/theme-common" "3.0.1" + "@docusaurus/theme-translations" "3.0.1" + "@docusaurus/utils" "3.0.1" + "@docusaurus/utils-validation" "3.0.1" + algoliasearch "^4.18.0" + algoliasearch-helper "^3.13.3" + clsx "^2.0.0" + eta "^2.2.0" + fs-extra "^11.1.1" + lodash "^4.17.21" + tslib "^2.6.0" + utility-types "^3.10.0" + +"@docusaurus/theme-translations@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-3.0.1.tgz#837a01a166ccd698a3eceaed0c2f798555bc024b" + integrity sha512-6UrbpzCTN6NIJnAtZ6Ne9492vmPVX+7Fsz4kmp+yor3KQwA1+MCzQP7ItDNkP38UmVLnvB/cYk/IvehCUqS3dg== + dependencies: + fs-extra "^11.1.1" + tslib "^2.6.0" + +"@docusaurus/tsconfig@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@docusaurus/tsconfig/-/tsconfig-3.0.1.tgz#170f230c34ff12e55995bd7e9f1f21db33035d8f" + integrity sha512-hT2HCdNE3pWTzXV/7cSsowfmaOxXVOTFOXmkqaYjBWjaxjJ3FO0nHbdJ8rF6Da7PvWmIPbUekdP5gep1XCJ7Vg== + +"@docusaurus/types@3.0.1", "@docusaurus/types@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-3.0.1.tgz#4fe306aa10ef7c97dbc07588864f6676a40f3b6f" + integrity sha512-plyX2iU1tcUsF46uQ01pAd4JhexR7n0iiQ5MSnBFX6M6NSJgDYdru/i1/YNPKOnQHBoXGLHv0dNT6OAlDWNjrg== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + commander "^5.1.0" + joi "^17.9.2" + react-helmet-async "^1.3.0" + utility-types "^3.10.0" + webpack "^5.88.1" + webpack-merge "^5.9.0" + +"@docusaurus/utils-common@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-3.0.1.tgz#111f450089d5f0a290c0c25f8a574a270d08436f" + integrity sha512-W0AxD6w6T8g6bNro8nBRWf7PeZ/nn7geEWM335qHU2DDDjHuV4UZjgUGP1AQsdcSikPrlIqTJJbKzer1lRSlIg== + dependencies: + tslib "^2.6.0" + +"@docusaurus/utils-validation@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-3.0.1.tgz#3c5f12941b328a19fc9acb34d070219f3e865ec6" + integrity sha512-ujTnqSfyGQ7/4iZdB4RRuHKY/Nwm58IIb+41s5tCXOv/MBU2wGAjOHq3U+AEyJ8aKQcHbxvTKJaRchNHYUVUQg== + dependencies: + "@docusaurus/logger" "3.0.1" + "@docusaurus/utils" "3.0.1" + joi "^17.9.2" + js-yaml "^4.1.0" + tslib "^2.6.0" + +"@docusaurus/utils@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-3.0.1.tgz#c64f68980a90c5bc6d53a5b8f32deb9026b1e303" + integrity sha512-TwZ33Am0q4IIbvjhUOs+zpjtD/mXNmLmEgeTGuRq01QzulLHuPhaBTTAC/DHu6kFx3wDgmgpAlaRuCHfTcXv8g== + dependencies: + "@docusaurus/logger" "3.0.1" + "@svgr/webpack" "^6.5.1" + escape-string-regexp "^4.0.0" + file-loader "^6.2.0" + fs-extra "^11.1.1" + github-slugger "^1.5.0" + globby "^11.1.0" + gray-matter "^4.0.3" + jiti "^1.20.0" + js-yaml "^4.1.0" + lodash "^4.17.21" + micromatch "^4.0.5" + resolve-pathname "^3.0.0" + shelljs "^0.8.5" + tslib "^2.6.0" + url-loader "^4.1.1" + webpack "^5.88.1" + +"@hapi/hoek@^9.0.0": + version "9.3.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" + integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== + +"@hapi/topo@^5.0.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" + integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@jest/schemas@^29.6.0": + version "29.6.0" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.0.tgz#0f4cb2c8e3dca80c135507ba5635a4fd755b0040" + integrity sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/types@^29.6.1": + version "29.6.1" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.1.tgz#ae79080278acff0a6af5eb49d063385aaa897bf2" + integrity sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw== + dependencies: + "@jest/schemas" "^29.6.0" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" + integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/source-map@^0.3.3": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.5.tgz#a3bb4d5c6825aab0d281268f47f6ad5853431e91" + integrity sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/sourcemap-codec@1.4.14": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.18" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6" + integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA== + dependencies: + "@jridgewell/resolve-uri" "3.1.0" + "@jridgewell/sourcemap-codec" "1.4.14" + +"@leichtgewicht/ip-codec@^2.0.1": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" + integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== + +"@mdx-js/mdx@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-3.0.0.tgz#37ef87685143fafedf1165f0a79e9fe95fbe5154" + integrity sha512-Icm0TBKBLYqroYbNW3BPnzMGn+7mwpQOK310aZ7+fkCtiU3aqv2cdcX+nd0Ydo3wI5Rx8bX2Z2QmGb/XcAClCw== + dependencies: + "@types/estree" "^1.0.0" + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdx" "^2.0.0" + collapse-white-space "^2.0.0" + devlop "^1.0.0" + estree-util-build-jsx "^3.0.0" + estree-util-is-identifier-name "^3.0.0" + estree-util-to-js "^2.0.0" + estree-walker "^3.0.0" + hast-util-to-estree "^3.0.0" + hast-util-to-jsx-runtime "^2.0.0" + markdown-extensions "^2.0.0" + periscopic "^3.0.0" + remark-mdx "^3.0.0" + remark-parse "^11.0.0" + remark-rehype "^11.0.0" + source-map "^0.7.0" + unified "^11.0.0" + unist-util-position-from-estree "^2.0.0" + unist-util-stringify-position "^4.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + +"@mdx-js/react@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@mdx-js/react/-/react-3.0.0.tgz#eaccaa8d6a7736b19080aff5a70448a7ba692271" + integrity sha512-nDctevR9KyYFyV+m+/+S4cpzCWHqj+iHDHq3QrsWezcC+B17uZdIWgCguESUkwFhM3n/56KxWVE3V6EokrmONQ== + dependencies: + "@types/mdx" "^2.0.0" + +"@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" + +"@pnpm/config.env-replace@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz#ab29da53df41e8948a00f2433f085f54de8b3a4c" + integrity sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w== + +"@pnpm/network.ca-file@^1.0.1": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz#2ab05e09c1af0cdf2fcf5035bea1484e222f7983" + integrity sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA== + dependencies: + graceful-fs "4.2.10" + +"@pnpm/npm-conf@^2.1.0": + version "2.2.2" + resolved "https://registry.yarnpkg.com/@pnpm/npm-conf/-/npm-conf-2.2.2.tgz#0058baf1c26cbb63a828f0193795401684ac86f0" + integrity sha512-UA91GwWPhFExt3IizW6bOeY/pQ0BkuNwKjk9iQW9KqxluGCrg4VenZ0/L+2Y0+ZOtme72EVvg6v0zo3AMQRCeA== + dependencies: + "@pnpm/config.env-replace" "^1.1.0" + "@pnpm/network.ca-file" "^1.0.1" + config-chain "^1.1.11" + +"@polka/url@^1.0.0-next.24": + version "1.0.0-next.24" + resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.24.tgz#58601079e11784d20f82d0585865bb42305c4df3" + integrity sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ== + +"@sideway/address@^4.1.3": + version "4.1.4" + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.4.tgz#03dccebc6ea47fdc226f7d3d1ad512955d4783f0" + integrity sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@sideway/formula@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" + integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== + +"@sideway/pinpoint@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" + integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@sindresorhus/is@^4.6.0": + version "4.6.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" + integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== + +"@sindresorhus/is@^5.2.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-5.6.0.tgz#41dd6093d34652cddb5d5bdeee04eafc33826668" + integrity sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g== + +"@slorber/react-ideal-image@^0.0.12": + version "0.0.12" + resolved "https://registry.yarnpkg.com/@slorber/react-ideal-image/-/react-ideal-image-0.0.12.tgz#5f867f9e10f2d82456568e8fd5bfb7673089c29c" + integrity sha512-u8KiDTEkMA7/KAeA5ywg/P7YG4zuKhWtswfVZDH8R8HXgQsFcHIYU2WaQnGuK/Du7Wdj90I+SdFmajSGFRvoKA== + +"@slorber/remark-comment@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@slorber/remark-comment/-/remark-comment-1.0.0.tgz#2a020b3f4579c89dec0361673206c28d67e08f5a" + integrity sha512-RCE24n7jsOj1M0UPvIQCHTe7fI0sFL4S2nwKVWwHyVr/wI/H8GosgsJGyhnsZoGFnD/P2hLf1mSbrrgSLN93NA== + dependencies: + micromark-factory-space "^1.0.0" + micromark-util-character "^1.1.0" + micromark-util-symbol "^1.0.1" + +"@slorber/static-site-generator-webpack-plugin@^4.0.7": + version "4.0.7" + resolved "https://registry.yarnpkg.com/@slorber/static-site-generator-webpack-plugin/-/static-site-generator-webpack-plugin-4.0.7.tgz#fc1678bddefab014e2145cbe25b3ce4e1cfc36f3" + integrity sha512-Ug7x6z5lwrz0WqdnNFOMYrDQNTPAprvHLSh6+/fmml3qUiz6l5eq+2MzLKWtn/q5K5NpSiFsZTP/fck/3vjSxA== + dependencies: + eval "^0.1.8" + p-map "^4.0.0" + webpack-sources "^3.2.2" + +"@svgr/babel-plugin-add-jsx-attribute@^6.5.1": + version "6.5.1" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.5.1.tgz#74a5d648bd0347bda99d82409d87b8ca80b9a1ba" + integrity sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ== + +"@svgr/babel-plugin-remove-jsx-attribute@*": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz#69177f7937233caca3a1afb051906698f2f59186" + integrity sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA== + +"@svgr/babel-plugin-remove-jsx-empty-expression@*": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz#c2c48104cfd7dcd557f373b70a56e9e3bdae1d44" + integrity sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA== + +"@svgr/babel-plugin-replace-jsx-attribute-value@^6.5.1": + version "6.5.1" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-6.5.1.tgz#fb9d22ea26d2bc5e0a44b763d4c46d5d3f596c60" + integrity sha512-8DPaVVE3fd5JKuIC29dqyMB54sA6mfgki2H2+swh+zNJoynC8pMPzOkidqHOSc6Wj032fhl8Z0TVn1GiPpAiJg== + +"@svgr/babel-plugin-svg-dynamic-title@^6.5.1": + version "6.5.1" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-6.5.1.tgz#01b2024a2b53ffaa5efceaa0bf3e1d5a4c520ce4" + integrity sha512-FwOEi0Il72iAzlkaHrlemVurgSQRDFbk0OC8dSvD5fSBPHltNh7JtLsxmZUhjYBZo2PpcU/RJvvi6Q0l7O7ogw== + +"@svgr/babel-plugin-svg-em-dimensions@^6.5.1": + version "6.5.1" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-6.5.1.tgz#dd3fa9f5b24eb4f93bcf121c3d40ff5facecb217" + integrity sha512-gWGsiwjb4tw+ITOJ86ndY/DZZ6cuXMNE/SjcDRg+HLuCmwpcjOktwRF9WgAiycTqJD/QXqL2f8IzE2Rzh7aVXA== + +"@svgr/babel-plugin-transform-react-native-svg@^6.5.1": + version "6.5.1" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-6.5.1.tgz#1d8e945a03df65b601551097d8f5e34351d3d305" + integrity sha512-2jT3nTayyYP7kI6aGutkyfJ7UMGtuguD72OjeGLwVNyfPRBD8zQthlvL+fAbAKk5n9ZNcvFkp/b1lZ7VsYqVJg== + +"@svgr/babel-plugin-transform-svg-component@^6.5.1": + version "6.5.1" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-6.5.1.tgz#48620b9e590e25ff95a80f811544218d27f8a250" + integrity sha512-a1p6LF5Jt33O3rZoVRBqdxL350oge54iZWHNI6LJB5tQ7EelvD/Mb1mfBiZNAan0dt4i3VArkFRjA4iObuNykQ== + +"@svgr/babel-preset@^6.5.1": + version "6.5.1" + resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-6.5.1.tgz#b90de7979c8843c5c580c7e2ec71f024b49eb828" + integrity sha512-6127fvO/FF2oi5EzSQOAjo1LE3OtNVh11R+/8FXa+mHx1ptAaS4cknIjnUA7e6j6fwGGJ17NzaTJFUwOV2zwCw== + dependencies: + "@svgr/babel-plugin-add-jsx-attribute" "^6.5.1" + "@svgr/babel-plugin-remove-jsx-attribute" "*" + "@svgr/babel-plugin-remove-jsx-empty-expression" "*" + "@svgr/babel-plugin-replace-jsx-attribute-value" "^6.5.1" + "@svgr/babel-plugin-svg-dynamic-title" "^6.5.1" + "@svgr/babel-plugin-svg-em-dimensions" "^6.5.1" + "@svgr/babel-plugin-transform-react-native-svg" "^6.5.1" + "@svgr/babel-plugin-transform-svg-component" "^6.5.1" + +"@svgr/core@^6.5.1": + version "6.5.1" + resolved "https://registry.yarnpkg.com/@svgr/core/-/core-6.5.1.tgz#d3e8aa9dbe3fbd747f9ee4282c1c77a27410488a" + integrity sha512-/xdLSWxK5QkqG524ONSjvg3V/FkNyCv538OIBdQqPNaAta3AsXj/Bd2FbvR87yMbXO2hFSWiAe/Q6IkVPDw+mw== + dependencies: + "@babel/core" "^7.19.6" + "@svgr/babel-preset" "^6.5.1" + "@svgr/plugin-jsx" "^6.5.1" + camelcase "^6.2.0" + cosmiconfig "^7.0.1" + +"@svgr/hast-util-to-babel-ast@^6.5.1": + version "6.5.1" + resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-6.5.1.tgz#81800bd09b5bcdb968bf6ee7c863d2288fdb80d2" + integrity sha512-1hnUxxjd83EAxbL4a0JDJoD3Dao3hmjvyvyEV8PzWmLK3B9m9NPlW7GKjFyoWE8nM7HnXzPcmmSyOW8yOddSXw== + dependencies: + "@babel/types" "^7.20.0" + entities "^4.4.0" + +"@svgr/plugin-jsx@^6.5.1": + version "6.5.1" + resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-6.5.1.tgz#0e30d1878e771ca753c94e69581c7971542a7072" + integrity sha512-+UdQxI3jgtSjCykNSlEMuy1jSRQlGC7pqBCPvkG/2dATdWo082zHTTK3uhnAju2/6XpE6B5mZ3z4Z8Ns01S8Gw== + dependencies: + "@babel/core" "^7.19.6" + "@svgr/babel-preset" "^6.5.1" + "@svgr/hast-util-to-babel-ast" "^6.5.1" + svg-parser "^2.0.4" + +"@svgr/plugin-svgo@^6.5.1": + version "6.5.1" + resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-6.5.1.tgz#0f91910e988fc0b842f88e0960c2862e022abe84" + integrity sha512-omvZKf8ixP9z6GWgwbtmP9qQMPX4ODXi+wzbVZgomNFsUIlHA1sf4fThdwTWSsZGgvGAG6yE+b/F5gWUkcZ/iQ== + dependencies: + cosmiconfig "^7.0.1" + deepmerge "^4.2.2" + svgo "^2.8.0" + +"@svgr/webpack@^6.5.1": + version "6.5.1" + resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-6.5.1.tgz#ecf027814fc1cb2decc29dc92f39c3cf691e40e8" + integrity sha512-cQ/AsnBkXPkEK8cLbv4Dm7JGXq2XrumKnL1dRpJD9rIO2fTIlJI9a1uCciYG1F2aUsox/hJQyNGbt3soDxSRkA== + dependencies: + "@babel/core" "^7.19.6" + "@babel/plugin-transform-react-constant-elements" "^7.18.12" + "@babel/preset-env" "^7.19.4" + "@babel/preset-react" "^7.18.6" + "@babel/preset-typescript" "^7.18.6" + "@svgr/core" "^6.5.1" + "@svgr/plugin-jsx" "^6.5.1" + "@svgr/plugin-svgo" "^6.5.1" + +"@szmarczak/http-timer@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-5.0.1.tgz#c7c1bf1141cdd4751b0399c8fc7b8b664cd5be3a" + integrity sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw== + dependencies: + defer-to-connect "^2.0.1" + +"@trysound/sax@0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" + integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== + +"@types/acorn@^4.0.0": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@types/acorn/-/acorn-4.0.6.tgz#d61ca5480300ac41a7d973dd5b84d0a591154a22" + integrity sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ== + dependencies: + "@types/estree" "*" + +"@types/body-parser@*": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/bonjour@^3.5.9": + version "3.5.10" + resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.10.tgz#0f6aadfe00ea414edc86f5d106357cda9701e275" + integrity sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw== + dependencies: + "@types/node" "*" + +"@types/connect-history-api-fallback@^1.3.5": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz#9fd20b3974bdc2bcd4ac6567e2e0f6885cb2cf41" + integrity sha512-4x5FkPpLipqwthjPsF7ZRbOv3uoLUFkTA9G9v583qi4pACvq0uTELrB8OLUzPWUI4IJIyvM85vzkV1nyiI2Lig== + dependencies: + "@types/express-serve-static-core" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + +"@types/debug@^4.0.0": + version "4.1.12" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" + integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== + dependencies: + "@types/ms" "*" + +"@types/eslint-scope@^3.7.3": + version "3.7.4" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16" + integrity sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*": + version "8.44.0" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.44.0.tgz#55818eabb376e2272f77fbf5c96c43137c3c1e53" + integrity sha512-gsF+c/0XOguWgaOgvFs+xnnRqt9GwgTvIks36WpE6ueeI4KCEHHd8K/CKHqhOqrJKsYH8m27kRzQEvWXAwXUTw== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree-jsx@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@types/estree-jsx/-/estree-jsx-1.0.3.tgz#f8aa833ec986d82b8271a294a92ed1565bf2c66a" + integrity sha512-pvQ+TKeRHeiUGRhvYwRrQ/ISnohKkSJR14fT2yqyZ4e9K5vqc7hrtY2Y1Dw0ZwAzQ6DQsxsaCUuSIIi8v0Cq6w== + dependencies: + "@types/estree" "*" + +"@types/estree@*", "@types/estree@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194" + integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA== + +"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33": + version "4.17.35" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz#c95dd4424f0d32e525d23812aa8ab8e4d3906c4f" + integrity sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@*", "@types/express@^4.17.13": + version "4.17.17" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.17.tgz#01d5437f6ef9cfa8668e616e13c2f2ac9a491ae4" + integrity sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.33" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/gtag.js@^0.0.12": + version "0.0.12" + resolved "https://registry.yarnpkg.com/@types/gtag.js/-/gtag.js-0.0.12.tgz#095122edca896689bdfcdd73b057e23064d23572" + integrity sha512-YQV9bUsemkzG81Ea295/nF/5GijnD2Af7QhEofh7xu+kvCN6RdodgNwwGWXB5GMI3NoyvQo0odNctoH/qLMIpg== + +"@types/hast@^3.0.0": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.3.tgz#7f75e6b43bc3f90316046a287d9ad3888309f7e1" + integrity sha512-2fYGlaDy/qyLlhidX42wAH0KBi2TCjKMH8CHmBXgRlJ3Y+OXTiqsPQ6IWarZKwF1JoUcAJdPogv1d4b0COTpmQ== + dependencies: + "@types/unist" "*" + +"@types/history@^4.7.11": + version "4.7.11" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64" + integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA== + +"@types/html-minifier-terser@^6.0.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35" + integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg== + +"@types/http-cache-semantics@^4.0.2": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4" + integrity sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA== + +"@types/http-errors@*": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.1.tgz#20172f9578b225f6c7da63446f56d4ce108d5a65" + integrity sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ== + +"@types/http-proxy@^1.17.8": + version "1.17.11" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.11.tgz#0ca21949a5588d55ac2b659b69035c84bd5da293" + integrity sha512-HC8G7c1WmaF2ekqpnFq626xd3Zz0uvaqFmBJNRZCGEZCXkvSdJoNFn/8Ygbd9fKNQj8UzLdCETaI0UWPAjK7IA== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" + integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== + +"@types/istanbul-lib-report@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" + integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" + integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": + version "7.0.12" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" + integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== + +"@types/mdast@^4.0.0", "@types/mdast@^4.0.2": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-4.0.3.tgz#1e011ff013566e919a4232d1701ad30d70cab333" + integrity sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg== + dependencies: + "@types/unist" "*" + +"@types/mdx@^2.0.0": + version "2.0.10" + resolved "https://registry.yarnpkg.com/@types/mdx/-/mdx-2.0.10.tgz#0d7b57fb1d83e27656156e4ee0dfba96532930e4" + integrity sha512-Rllzc5KHk0Al5/WANwgSPl1/CwjqCy+AZrGd78zuK+jO9aDM6ffblZ+zIjgPNAaEBmlO0RYDvLNh7wD0zKVgEg== + +"@types/mime@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" + integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== + +"@types/mime@^1": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" + integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== + +"@types/ms@*": + version "0.7.34" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.34.tgz#10964ba0dee6ac4cd462e2795b6bebd407303433" + integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g== + +"@types/node@*": + version "20.4.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.4.tgz#c79c7cc22c9d0e97a7944954c9e663bcbd92b0cb" + integrity sha512-CukZhumInROvLq3+b5gLev+vgpsIqC2D0deQr/yS1WnxvmYLlJXZpaQrQiseMY+6xusl79E04UjWoqyr+t1/Ew== + +"@types/node@^17.0.5": + version "17.0.45" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190" + integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw== + +"@types/parse-json@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" + integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + +"@types/prismjs@^1.26.0": + version "1.26.3" + resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.26.3.tgz#47fe8e784c2dee24fe636cab82e090d3da9b7dec" + integrity sha512-A0D0aTXvjlqJ5ZILMz3rNfDBOx9hHxLZYv2by47Sm/pqW35zzjusrZTryatjN/Rf8Us2gZrJD+KeHbUSTux1Cw== + +"@types/prop-types@*": + version "15.7.5" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" + integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== + +"@types/qs@*": + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + +"@types/range-parser@*": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + +"@types/react-router-config@*": + version "5.0.7" + resolved "https://registry.yarnpkg.com/@types/react-router-config/-/react-router-config-5.0.7.tgz#36207a3fe08b271abee62b26993ee932d13cbb02" + integrity sha512-pFFVXUIydHlcJP6wJm7sDii5mD/bCmmAY0wQzq+M+uX7bqS95AQqHZWP1iNMKrWVQSuHIzj5qi9BvrtLX2/T4w== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-router" "^5.1.0" + +"@types/react-router-config@^5.0.7": + version "5.0.11" + resolved "https://registry.yarnpkg.com/@types/react-router-config/-/react-router-config-5.0.11.tgz#2761a23acc7905a66a94419ee40294a65aaa483a" + integrity sha512-WmSAg7WgqW7m4x8Mt4N6ZyKz0BubSj/2tVUMsAHp+Yd2AMwcSbeFq9WympT19p5heCFmF97R9eD5uUR/t4HEqw== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-router" "^5.1.0" + +"@types/react-router-dom@*": + version "5.3.3" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83" + integrity sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router@*", "@types/react-router@^5.1.0": + version "5.1.20" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.20.tgz#88eccaa122a82405ef3efbcaaa5dcdd9f021387c" + integrity sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + +"@types/react@*": + version "18.2.15" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.15.tgz#14792b35df676c20ec3cf595b262f8c615a73066" + integrity sha512-oEjE7TQt1fFTFSbf8kkNuc798ahTUzn3Le67/PWjE8MAfYAD/qB7O8hSTcromLFqHCt9bcdOg5GXMokzTjJ5SA== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + +"@types/retry@0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" + integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== + +"@types/sax@^1.2.1": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/sax/-/sax-1.2.4.tgz#8221affa7f4f3cb21abd22f244cfabfa63e6a69e" + integrity sha512-pSAff4IAxJjfAXUG6tFkO7dsSbTmf8CtUpfhhZ5VhkRpC4628tJhh3+V6H1E+/Gs9piSzYKT5yzHO5M4GG9jkw== + dependencies: + "@types/node" "*" + +"@types/scheduler@*": + version "0.16.3" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5" + integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ== + +"@types/send@*": + version "0.17.1" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.1.tgz#ed4932b8a2a805f1fe362a70f4e62d0ac994e301" + integrity sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/serve-index@^1.9.1": + version "1.9.1" + resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.1.tgz#1b5e85370a192c01ec6cec4735cf2917337a6278" + integrity sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg== + dependencies: + "@types/express" "*" + +"@types/serve-static@*", "@types/serve-static@^1.13.10": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.2.tgz#3e5419ecd1e40e7405d34093f10befb43f63381a" + integrity sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw== + dependencies: + "@types/http-errors" "*" + "@types/mime" "*" + "@types/node" "*" + +"@types/sockjs@^0.3.33": + version "0.3.33" + resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.33.tgz#570d3a0b99ac995360e3136fd6045113b1bd236f" + integrity sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw== + dependencies: + "@types/node" "*" + +"@types/unist@*", "@types/unist@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.2.tgz#6dd61e43ef60b34086287f83683a5c1b2dc53d20" + integrity sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ== + +"@types/unist@^2.0.0": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.7.tgz#5b06ad6894b236a1d2bd6b2f07850ca5c59cf4d6" + integrity sha512-cputDpIbFgLUaGQn6Vqg3/YsJwxUwHLO13v3i5ouxT4lat0khip9AEWxtERujXV9wxIB1EyF97BSJFt6vpdI8g== + +"@types/ws@^8.5.5": + version "8.5.5" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.5.tgz#af587964aa06682702ee6dcbc7be41a80e4b28eb" + integrity sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg== + dependencies: + "@types/node" "*" + +"@types/yargs-parser@*": + version "21.0.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" + integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== + +"@types/yargs@^17.0.8": + version "17.0.24" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.24.tgz#b3ef8d50ad4aa6aecf6ddc97c580a00f5aa11902" + integrity sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw== + dependencies: + "@types/yargs-parser" "*" + +"@ungap/structured-clone@^1.0.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + +"@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz#db046555d3c413f8966ca50a95176a0e2c642e24" + integrity sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q== + dependencies: + "@webassemblyjs/helper-numbers" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + +"@webassemblyjs/floating-point-hex-parser@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz#dacbcb95aff135c8260f77fa3b4c5fea600a6431" + integrity sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw== + +"@webassemblyjs/helper-api-error@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz#6132f68c4acd59dcd141c44b18cbebbd9f2fa768" + integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q== + +"@webassemblyjs/helper-buffer@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz#b66d73c43e296fd5e88006f18524feb0f2c7c093" + integrity sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA== + +"@webassemblyjs/helper-numbers@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz#cbce5e7e0c1bd32cf4905ae444ef64cea919f1b5" + integrity sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.11.6" + "@webassemblyjs/helper-api-error" "1.11.6" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/helper-wasm-bytecode@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz#bb2ebdb3b83aa26d9baad4c46d4315283acd51e9" + integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA== + +"@webassemblyjs/helper-wasm-section@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz#ff97f3863c55ee7f580fd5c41a381e9def4aa577" + integrity sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g== + dependencies: + "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/helper-buffer" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/wasm-gen" "1.11.6" + +"@webassemblyjs/ieee754@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz#bb665c91d0b14fffceb0e38298c329af043c6e3a" + integrity sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.6.tgz#70e60e5e82f9ac81118bc25381a0b283893240d7" + integrity sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a" + integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== + +"@webassemblyjs/wasm-edit@^1.11.5": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz#c72fa8220524c9b416249f3d94c2958dfe70ceab" + integrity sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw== + dependencies: + "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/helper-buffer" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/helper-wasm-section" "1.11.6" + "@webassemblyjs/wasm-gen" "1.11.6" + "@webassemblyjs/wasm-opt" "1.11.6" + "@webassemblyjs/wasm-parser" "1.11.6" + "@webassemblyjs/wast-printer" "1.11.6" + +"@webassemblyjs/wasm-gen@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz#fb5283e0e8b4551cc4e9c3c0d7184a65faf7c268" + integrity sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA== + dependencies: + "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" + +"@webassemblyjs/wasm-opt@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz#d9a22d651248422ca498b09aa3232a81041487c2" + integrity sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g== + dependencies: + "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/helper-buffer" "1.11.6" + "@webassemblyjs/wasm-gen" "1.11.6" + "@webassemblyjs/wasm-parser" "1.11.6" + +"@webassemblyjs/wasm-parser@1.11.6", "@webassemblyjs/wasm-parser@^1.11.5": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz#bb85378c527df824004812bbdb784eea539174a1" + integrity sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ== + dependencies: + "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/helper-api-error" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" + +"@webassemblyjs/wast-printer@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz#a7bf8dd7e362aeb1668ff43f35cb849f188eff20" + integrity sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A== + dependencies: + "@webassemblyjs/ast" "1.11.6" + "@xtuc/long" "4.2.2" + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + +accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +acorn-import-assertions@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" + integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== + +acorn-jsx@^5.0.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-walk@^8.0.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + +acorn@^8.0.0: + version "8.11.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b" + integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== + +acorn@^8.0.4, acorn@^8.7.1, acorn@^8.8.2: + version "8.10.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" + integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== + +address@^1.0.1, address@^1.1.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/address/-/address-1.2.2.tgz#2b5248dac5485a6390532c6a517fda2e3faac89e" + integrity sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA== + +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-formats@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== + dependencies: + ajv "^8.0.0" + +ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== + +ajv-keywords@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" + integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== + dependencies: + fast-deep-equal "^3.1.3" + +ajv@^6.12.2, ajv@^6.12.5: + 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" + +ajv@^8.0.0, ajv@^8.9.0: + version "8.12.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" + integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + +algoliasearch-helper@^3.13.3: + version "3.16.1" + resolved "https://registry.yarnpkg.com/algoliasearch-helper/-/algoliasearch-helper-3.16.1.tgz#421e3554ec86e14e60e7e0bf796aef61cf4a06ec" + integrity sha512-qxAHVjjmT7USVvrM8q6gZGaJlCK1fl4APfdAA7o8O6iXEc68G0xMNrzRkxoB/HmhhvyHnoteS/iMTiHiTcQQcg== + dependencies: + "@algolia/events" "^4.0.1" + +algoliasearch@^4.18.0, algoliasearch@^4.19.1: + version "4.22.0" + resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.22.0.tgz#9ece4446b5ab0af941ef97553c18ddcd1b8040a5" + integrity sha512-gfceltjkwh7PxXwtkS8KVvdfK+TSNQAWUeNSxf4dA29qW5tf2EGwa8jkJujlT9jLm17cixMVoGNc+GJFO1Mxhg== + dependencies: + "@algolia/cache-browser-local-storage" "4.22.0" + "@algolia/cache-common" "4.22.0" + "@algolia/cache-in-memory" "4.22.0" + "@algolia/client-account" "4.22.0" + "@algolia/client-analytics" "4.22.0" + "@algolia/client-common" "4.22.0" + "@algolia/client-personalization" "4.22.0" + "@algolia/client-search" "4.22.0" + "@algolia/logger-common" "4.22.0" + "@algolia/logger-console" "4.22.0" + "@algolia/requester-browser-xhr" "4.22.0" + "@algolia/requester-common" "4.22.0" + "@algolia/requester-node-http" "4.22.0" + "@algolia/transporter" "4.22.0" + +ansi-align@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" + integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== + dependencies: + string-width "^4.1.0" + +ansi-html-community@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" + integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== + +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-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== + +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.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" + +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^5.0.0: + version "5.0.2" + resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" + integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +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== + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + +array-flatten@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" + integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== + +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== + +astring@^1.8.0: + version "1.8.6" + resolved "https://registry.yarnpkg.com/astring/-/astring-1.8.6.tgz#2c9c157cf1739d67561c56ba896e6948f6b93731" + integrity sha512-ISvCdHdlTDlH5IpxQJIex7BWBywFWgjJSVdwst+/iQCoEYnyOaQ95+X1JGshuBjGp6nxKUy1jMgE3zPqN7fQdg== + +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + +autoprefixer@^10.4.12: + version "10.4.14" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.14.tgz#e28d49902f8e759dd25b153264e862df2705f79d" + integrity sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ== + dependencies: + browserslist "^4.21.5" + caniuse-lite "^1.0.30001464" + fraction.js "^4.2.0" + normalize-range "^0.1.2" + picocolors "^1.0.0" + postcss-value-parser "^4.2.0" + +autoprefixer@^10.4.14: + version "10.4.16" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.16.tgz#fad1411024d8670880bdece3970aa72e3572feb8" + integrity sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ== + dependencies: + browserslist "^4.21.10" + caniuse-lite "^1.0.30001538" + fraction.js "^4.3.6" + normalize-range "^0.1.2" + picocolors "^1.0.0" + postcss-value-parser "^4.2.0" + +b4a@^1.6.4: + version "1.6.4" + resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.4.tgz#ef1c1422cae5ce6535ec191baeed7567443f36c9" + integrity sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw== + +babel-loader@^9.1.3: + version "9.1.3" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-9.1.3.tgz#3d0e01b4e69760cc694ee306fe16d358aa1c6f9a" + integrity sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw== + dependencies: + find-cache-dir "^4.0.0" + schema-utils "^4.0.0" + +babel-plugin-dynamic-import-node@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" + integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== + dependencies: + object.assign "^4.1.0" + +babel-plugin-polyfill-corejs2@^0.4.4: + version "0.4.5" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.5.tgz#8097b4cb4af5b64a1d11332b6fb72ef5e64a054c" + integrity sha512-19hwUH5FKl49JEsvyTcoHakh6BE0wgXLLptIyKZ3PijHc/Ci521wygORCUCCred+E/twuqRyAkE02BAWPmsHOg== + dependencies: + "@babel/compat-data" "^7.22.6" + "@babel/helper-define-polyfill-provider" "^0.4.2" + semver "^6.3.1" + +babel-plugin-polyfill-corejs2@^0.4.6: + version "0.4.7" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.7.tgz#679d1b94bf3360f7682e11f2cb2708828a24fe8c" + integrity sha512-LidDk/tEGDfuHW2DWh/Hgo4rmnw3cduK6ZkOI1NPFceSK3n/yAGeOsNT7FLnSGHkXj3RHGSEVkN3FsCTY6w2CQ== + dependencies: + "@babel/compat-data" "^7.22.6" + "@babel/helper-define-polyfill-provider" "^0.4.4" + semver "^6.3.1" + +babel-plugin-polyfill-corejs3@^0.8.2: + version "0.8.3" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.3.tgz#b4f719d0ad9bb8e0c23e3e630c0c8ec6dd7a1c52" + integrity sha512-z41XaniZL26WLrvjy7soabMXrfPWARN25PZoriDEiLMxAp50AUW3t35BGQUMg5xK3UrpVTtagIDklxYa+MhiNA== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.4.2" + core-js-compat "^3.31.0" + +babel-plugin-polyfill-corejs3@^0.8.5: + version "0.8.7" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.7.tgz#941855aa7fdaac06ed24c730a93450d2b2b76d04" + integrity sha512-KyDvZYxAzkC0Aj2dAPyDzi2Ym15e5JKZSK+maI7NAwSqofvuFglbSsxE7wUOvTg9oFVnHMzVzBKcqEb4PJgtOA== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.4.4" + core-js-compat "^3.33.1" + +babel-plugin-polyfill-regenerator@^0.5.1: + version "0.5.2" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.2.tgz#80d0f3e1098c080c8b5a65f41e9427af692dc326" + integrity sha512-tAlOptU0Xj34V1Y2PNTL4Y0FOJMDB6bZmoW39FeCQIhigGLkqu3Fj6uiXpxIf6Ij274ENdYx64y6Au+ZKlb1IA== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.4.2" + +babel-plugin-polyfill-regenerator@^0.5.3: + version "0.5.4" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.4.tgz#c6fc8eab610d3a11eb475391e52584bacfc020f4" + integrity sha512-S/x2iOCvDaCASLYsOOgWOq4bCfKYVqvO/uxjkaYyZ3rVsVE3CeAI/c84NpyuBBymEgNvHgjEot3a9/Z/kXvqsg== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.4.4" + +bail@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/bail/-/bail-2.0.2.tgz#d26f5cd8fe5d6f832a31517b9f7c356040ba6d5d" + integrity sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw== + +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== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +batch@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" + integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== + +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +bl@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + +body-parser@1.20.1: + version "1.20.1" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" + integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== + dependencies: + bytes "3.1.2" + content-type "~1.0.4" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.11.0" + raw-body "2.5.1" + type-is "~1.6.18" + unpipe "1.0.0" + +bonjour-service@^1.0.11: + version "1.1.1" + resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.1.1.tgz#960948fa0e0153f5d26743ab15baf8e33752c135" + integrity sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg== + dependencies: + array-flatten "^2.1.2" + dns-equal "^1.0.0" + fast-deep-equal "^3.1.3" + multicast-dns "^7.2.5" + +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + +boxen@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-6.2.1.tgz#b098a2278b2cd2845deef2dff2efc38d329b434d" + integrity sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw== + dependencies: + ansi-align "^3.0.1" + camelcase "^6.2.0" + chalk "^4.1.2" + cli-boxes "^3.0.0" + string-width "^5.0.1" + type-fest "^2.5.0" + widest-line "^4.0.1" + wrap-ansi "^8.0.1" + +boxen@^7.0.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-7.1.1.tgz#f9ba525413c2fec9cdb88987d835c4f7cad9c8f4" + integrity sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog== + dependencies: + ansi-align "^3.0.1" + camelcase "^7.0.1" + chalk "^5.2.0" + cli-boxes "^3.0.0" + string-width "^5.1.2" + type-fest "^2.13.0" + widest-line "^4.0.1" + wrap-ansi "^8.1.0" + +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.2, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.18.1, browserslist@^4.21.4, browserslist@^4.21.5, browserslist@^4.21.9: + version "4.21.9" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.9.tgz#e11bdd3c313d7e2a9e87e8b4b0c7872b13897635" + integrity sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg== + dependencies: + caniuse-lite "^1.0.30001503" + electron-to-chromium "^1.4.431" + node-releases "^2.0.12" + update-browserslist-db "^1.0.11" + +browserslist@^4.21.10, browserslist@^4.22.2: + version "4.22.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.2.tgz#704c4943072bd81ea18997f3bd2180e89c77874b" + integrity sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A== + dependencies: + caniuse-lite "^1.0.30001565" + electron-to-chromium "^1.4.601" + node-releases "^2.0.14" + update-browserslist-db "^1.0.13" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +cacheable-lookup@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz#3476a8215d046e5a3202a9209dd13fec1f933a27" + integrity sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w== + +cacheable-request@^10.2.8: + version "10.2.14" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-10.2.14.tgz#eb915b665fda41b79652782df3f553449c406b9d" + integrity sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ== + dependencies: + "@types/http-cache-semantics" "^4.0.2" + get-stream "^6.0.1" + http-cache-semantics "^4.1.1" + keyv "^4.5.3" + mimic-response "^4.0.0" + normalize-url "^8.0.0" + responselike "^3.0.0" + +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== + +camel-case@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a" + integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw== + dependencies: + pascal-case "^3.1.2" + tslib "^2.0.3" + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +camelcase@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-7.0.1.tgz#f02e50af9fd7782bc8b88a3558c32fd3a388f048" + integrity sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw== + +caniuse-api@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" + integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== + dependencies: + browserslist "^4.0.0" + caniuse-lite "^1.0.0" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" + +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001464, caniuse-lite@^1.0.30001503: + version "1.0.30001517" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001517.tgz#90fabae294215c3495807eb24fc809e11dc2f0a8" + integrity sha512-Vdhm5S11DaFVLlyiKu4hiUTkpZu+y1KA/rZZqVQfOD5YdDT/eQKlkt7NaE0WGOFgX32diqt9MiP9CAiFeRklaA== + +caniuse-lite@^1.0.30001538, caniuse-lite@^1.0.30001565: + version "1.0.30001571" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001571.tgz#4182e93d696ff42930f4af7eba515ddeb57917ac" + integrity sha512-tYq/6MoXhdezDLFZuCO/TKboTzuQ/xR5cFdgXPfDtM7/kchBO3b4VWghE/OAi/DV7tTdhmLjZiZBZi1fA/GheQ== + +ccount@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" + integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== + +chalk@^2.0.0, chalk@^2.4.2: + 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.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" + +chalk@^5.0.1, chalk@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" + integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +character-entities-html4@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz#1f1adb940c971a4b22ba39ddca6b618dc6e56b2b" + integrity sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA== + +character-entities-legacy@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz#76bc83a90738901d7bc223a9e93759fdd560125b" + integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ== + +character-entities@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.2.tgz#2d09c2e72cd9523076ccb21157dff66ad43fcc22" + integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ== + +character-reference-invalid@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9" + integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw== + +cheerio-select@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-2.1.0.tgz#4d8673286b8126ca2a8e42740d5e3c4884ae21b4" + integrity sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g== + dependencies: + boolbase "^1.0.0" + css-select "^5.1.0" + css-what "^6.1.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.0.1" + +cheerio@^1.0.0-rc.12: + version "1.0.0-rc.12" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.12.tgz#788bf7466506b1c6bf5fae51d24a2c4d62e47683" + integrity sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q== + dependencies: + cheerio-select "^2.1.0" + dom-serializer "^2.0.0" + domhandler "^5.0.3" + domutils "^3.0.1" + htmlparser2 "^8.0.1" + parse5 "^7.0.0" + parse5-htmlparser2-tree-adapter "^7.0.0" + +chokidar@^3.4.2, chokidar@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + +chrome-trace-event@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" + integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== + +ci-info@^3.2.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91" + integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== + +clean-css@^5.2.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.2.tgz#70ecc7d4d4114921f5d298349ff86a31a9975224" + integrity sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww== + dependencies: + source-map "~0.6.0" + +clean-css@^5.3.2, clean-css@~5.3.2: + version "5.3.3" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.3.tgz#b330653cd3bd6b75009cc25c714cae7b93351ccd" + integrity sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg== + dependencies: + source-map "~0.6.0" + +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== + +cli-boxes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-3.0.0.tgz#71a10c716feeba005e4504f36329ef0b17cf3145" + integrity sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g== + +cli-table3@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.3.tgz#61ab765aac156b52f222954ffc607a6f01dbeeb2" + integrity sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg== + dependencies: + string-width "^4.2.0" + optionalDependencies: + "@colors/colors" "1.5.0" + +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + +clsx@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.0.0.tgz#12658f3fd98fafe62075595a5c30e43d18f3d00b" + integrity sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q== + +collapse-white-space@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-2.1.0.tgz#640257174f9f42c740b40f3b55ee752924feefca" + integrity sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw== + +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 sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@^1.0.0, 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-string@^1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" + integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" + integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== + dependencies: + color-convert "^2.0.1" + color-string "^1.9.0" + +colord@^2.9.1: + version "2.9.3" + resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" + integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== + +colorette@^2.0.10: + version "2.0.20" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + +combine-promises@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/combine-promises/-/combine-promises-1.1.0.tgz#72db90743c0ca7aab7d0d8d2052fd7b0f674de71" + integrity sha512-ZI9jvcLDxqwaXEixOhArm3r7ReIivsXkpbyEWyeOhzz1QS0iSgBPnWvEqvIQtYyamGCYA88gFhmUrs9hrrQ0pg== + +comma-separated-tokens@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee" + integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== + +commander@^10.0.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" + integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== + +commander@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + +commander@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" + integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== + +common-path-prefix@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/common-path-prefix/-/common-path-prefix-3.0.0.tgz#7d007a7e07c58c4b4d5f433131a19141b29f11e0" + integrity sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w== + +compressible@~2.0.16: + version "2.0.18" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" + integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== + dependencies: + mime-db ">= 1.43.0 < 2" + +compression@^1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" + integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== + dependencies: + accepts "~1.3.5" + bytes "3.0.0" + compressible "~2.0.16" + debug "2.6.9" + on-headers "~1.0.2" + safe-buffer "5.1.2" + vary "~1.1.2" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +config-chain@^1.1.11: + version "1.1.13" + resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4" + integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ== + dependencies: + ini "^1.3.4" + proto-list "~1.2.1" + +configstore@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-6.0.0.tgz#49eca2ebc80983f77e09394a1a56e0aca8235566" + integrity sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA== + dependencies: + dot-prop "^6.0.1" + graceful-fs "^4.2.6" + unique-string "^3.0.0" + write-file-atomic "^3.0.3" + xdg-basedir "^5.0.1" + +connect-history-api-fallback@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8" + integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA== + +consola@^2.15.3: + version "2.15.3" + resolved "https://registry.yarnpkg.com/consola/-/consola-2.15.3.tgz#2e11f98d6a4be71ff72e0bdf07bd23e12cb61550" + integrity sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw== + +"consolidated-events@^1.1.0 || ^2.0.0": + version "2.0.2" + resolved "https://registry.yarnpkg.com/consolidated-events/-/consolidated-events-2.0.2.tgz#da8d8f8c2b232831413d9e190dc11669c79f4a91" + integrity sha512-2/uRVMdRypf5z/TW/ncD/66l75P5hH2vM/GR8Jf8HLc2xnfJtmina6F6du8+v4Z2vTrMo7jC+W1tmEEuuELgkQ== + +content-disposition@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" + integrity sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA== + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +convert-source-map@^1.7.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" + integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== + +copy-text-to-clipboard@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.0.tgz#0202b2d9bdae30a49a53f898626dcc3b49ad960b" + integrity sha512-RnJFp1XR/LOBDckxTib5Qjr/PMfkatD0MUCQgdpqS8MdKiNUzBjAQBEN6oUy+jW7LI93BBG3DtMB2KOOKpGs2Q== + +copy-webpack-plugin@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz#96d4dbdb5f73d02dd72d0528d1958721ab72e04a" + integrity sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ== + dependencies: + fast-glob "^3.2.11" + glob-parent "^6.0.1" + globby "^13.1.1" + normalize-path "^3.0.0" + schema-utils "^4.0.0" + serialize-javascript "^6.0.0" + +core-js-compat@^3.31.0: + version "3.31.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.31.1.tgz#5084ad1a46858df50ff89ace152441a63ba7aae0" + integrity sha512-wIDWd2s5/5aJSdpOJHfSibxNODxoGoWOBHt8JSPB41NOE94M7kuTPZCYLOlTtuoXTsBPKobpJ6T+y0SSy5L9SA== + dependencies: + browserslist "^4.21.9" + +core-js-compat@^3.33.1: + version "3.34.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.34.0.tgz#61a4931a13c52f8f08d924522bba65f8c94a5f17" + integrity sha512-4ZIyeNbW/Cn1wkMMDy+mvrRUxrwFNjKwbhCfQpDd+eLgYipDqp8oGFGtLmhh18EDPKA0g3VUBYOxQGGwvWLVpA== + dependencies: + browserslist "^4.22.2" + +core-js-pure@^3.30.2: + version "3.31.1" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.31.1.tgz#73d154958881873bc19381df80bddb20c8d0cdb5" + integrity sha512-w+C62kvWti0EPs4KPMCMVv9DriHSXfQOCQ94bGGBiEW5rrbtt/Rz8n5Krhfw9cpFyzXBjf3DB3QnPdEzGDY4Fw== + +core-js@^3.31.1: + version "3.34.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.34.0.tgz#5705e6ad5982678612e96987d05b27c6c7c274a5" + integrity sha512-aDdvlDder8QmY91H88GzNi9EtQi2TjvQhpCX6B1v/dAZHU1AuLgHvRh54RiOerpEhEW46Tkf+vgAViB/CWC0ag== + +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== + +cosmiconfig@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" + integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.1.0" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.7.2" + +cosmiconfig@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" + integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + +cosmiconfig@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.2.0.tgz#f7d17c56a590856cd1e7cee98734dca272b0d8fd" + integrity sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ== + dependencies: + import-fresh "^3.2.1" + js-yaml "^4.1.0" + parse-json "^5.0.0" + path-type "^4.0.0" + +cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +crypto-random-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-4.0.0.tgz#5a3cc53d7dd86183df5da0312816ceeeb5bb1fc2" + integrity sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA== + dependencies: + type-fest "^1.0.1" + +css-declaration-sorter@^6.3.1: + version "6.4.1" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz#28beac7c20bad7f1775be3a7129d7eae409a3a71" + integrity sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g== + +css-loader@^6.8.1: + version "6.8.1" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.8.1.tgz#0f8f52699f60f5e679eab4ec0fcd68b8e8a50a88" + integrity sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g== + dependencies: + icss-utils "^5.1.0" + postcss "^8.4.21" + postcss-modules-extract-imports "^3.0.0" + postcss-modules-local-by-default "^4.0.3" + postcss-modules-scope "^3.0.0" + postcss-modules-values "^4.0.0" + postcss-value-parser "^4.2.0" + semver "^7.3.8" + +css-minimizer-webpack-plugin@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-4.2.2.tgz#79f6199eb5adf1ff7ba57f105e3752d15211eb35" + integrity sha512-s3Of/4jKfw1Hj9CxEO1E5oXhQAxlayuHO2y/ML+C6I9sQ7FdzfEV6QgMLN3vI+qFsjJGIAFLKtQK7t8BOXAIyA== + dependencies: + cssnano "^5.1.8" + jest-worker "^29.1.2" + postcss "^8.4.17" + schema-utils "^4.0.0" + serialize-javascript "^6.0.0" + source-map "^0.6.1" + +css-select@^4.1.3: + version "4.3.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b" + integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ== + dependencies: + boolbase "^1.0.0" + css-what "^6.0.1" + domhandler "^4.3.1" + domutils "^2.8.0" + nth-check "^2.0.1" + +css-select@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" + integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== + dependencies: + boolbase "^1.0.0" + css-what "^6.1.0" + domhandler "^5.0.2" + domutils "^3.0.1" + nth-check "^2.0.1" + +css-tree@^1.1.2, css-tree@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" + integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== + dependencies: + mdn-data "2.0.14" + source-map "^0.6.1" + +css-what@^6.0.1, css-what@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" + integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +cssnano-preset-advanced@^5.3.10: + version "5.3.10" + resolved "https://registry.yarnpkg.com/cssnano-preset-advanced/-/cssnano-preset-advanced-5.3.10.tgz#25558a1fbf3a871fb6429ce71e41be7f5aca6eef" + integrity sha512-fnYJyCS9jgMU+cmHO1rPSPf9axbQyD7iUhLO5Df6O4G+fKIOMps+ZbU0PdGFejFBBZ3Pftf18fn1eG7MAPUSWQ== + dependencies: + autoprefixer "^10.4.12" + cssnano-preset-default "^5.2.14" + postcss-discard-unused "^5.1.0" + postcss-merge-idents "^5.1.1" + postcss-reduce-idents "^5.2.0" + postcss-zindex "^5.1.0" + +cssnano-preset-default@^5.2.14: + version "5.2.14" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz#309def4f7b7e16d71ab2438052093330d9ab45d8" + integrity sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A== + dependencies: + css-declaration-sorter "^6.3.1" + cssnano-utils "^3.1.0" + postcss-calc "^8.2.3" + postcss-colormin "^5.3.1" + postcss-convert-values "^5.1.3" + postcss-discard-comments "^5.1.2" + postcss-discard-duplicates "^5.1.0" + postcss-discard-empty "^5.1.1" + postcss-discard-overridden "^5.1.0" + postcss-merge-longhand "^5.1.7" + postcss-merge-rules "^5.1.4" + postcss-minify-font-values "^5.1.0" + postcss-minify-gradients "^5.1.1" + postcss-minify-params "^5.1.4" + postcss-minify-selectors "^5.2.1" + postcss-normalize-charset "^5.1.0" + postcss-normalize-display-values "^5.1.0" + postcss-normalize-positions "^5.1.1" + postcss-normalize-repeat-style "^5.1.1" + postcss-normalize-string "^5.1.0" + postcss-normalize-timing-functions "^5.1.0" + postcss-normalize-unicode "^5.1.1" + postcss-normalize-url "^5.1.0" + postcss-normalize-whitespace "^5.1.1" + postcss-ordered-values "^5.1.3" + postcss-reduce-initial "^5.1.2" + postcss-reduce-transforms "^5.1.0" + postcss-svgo "^5.1.0" + postcss-unique-selectors "^5.1.1" + +cssnano-utils@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.1.0.tgz#95684d08c91511edfc70d2636338ca37ef3a6861" + integrity sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA== + +cssnano@^5.1.15, cssnano@^5.1.8: + version "5.1.15" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.15.tgz#ded66b5480d5127fcb44dac12ea5a983755136bf" + integrity sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw== + dependencies: + cssnano-preset-default "^5.2.14" + lilconfig "^2.0.3" + yaml "^1.10.2" + +csso@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" + integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== + dependencies: + css-tree "^1.1.2" + +csstype@^3.0.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" + integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== + +debounce@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" + integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug== + +debug@2.6.9, debug@^2.6.0: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +decode-named-character-reference@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz#daabac9690874c394c81e4162a0304b35d824f0e" + integrity sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg== + dependencies: + character-entities "^2.0.0" + +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +default-gateway@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71" + integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg== + dependencies: + execa "^5.0.0" + +defer-to-connect@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" + integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== + +define-lazy-prop@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" + integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== + +define-properties@^1.1.4: + version "1.2.0" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.0.tgz#52988570670c9eacedd8064f4a990f2405849bd5" + integrity sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA== + dependencies: + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +del@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/del/-/del-6.1.1.tgz#3b70314f1ec0aa325c6b14eb36b95786671edb7a" + integrity sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg== + dependencies: + globby "^11.0.1" + graceful-fs "^4.2.4" + is-glob "^4.0.1" + is-path-cwd "^2.2.0" + is-path-inside "^3.0.2" + p-map "^4.0.0" + rimraf "^3.0.2" + slash "^3.0.0" + +depd@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== + +dequal@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +detect-libc@^2.0.0, detect-libc@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.2.tgz#8ccf2ba9315350e1241b88d0ac3b0e1fbd99605d" + integrity sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw== + +detect-node@^2.0.4: + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" + integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== + +detect-port-alt@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/detect-port-alt/-/detect-port-alt-1.1.6.tgz#24707deabe932d4a3cf621302027c2b266568275" + integrity sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q== + dependencies: + address "^1.0.1" + debug "^2.6.0" + +detect-port@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/detect-port/-/detect-port-1.5.1.tgz#451ca9b6eaf20451acb0799b8ab40dff7718727b" + integrity sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ== + dependencies: + address "^1.0.1" + debug "4" + +devlop@^1.0.0, devlop@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/devlop/-/devlop-1.1.0.tgz#4db7c2ca4dc6e0e834c30be70c94bbc976dc7018" + integrity sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA== + dependencies: + dequal "^2.0.0" + +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" + +dns-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" + integrity sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg== + +dns-packet@^5.2.2: + version "5.6.0" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.6.0.tgz#2202c947845c7a63c23ece58f2f70ff6ab4c2f7d" + integrity sha512-rza3UH1LwdHh9qyPXp8lkwpjSNk/AMD3dPytUoRoqnypDUhY0xvbdmVhWOfxO68frEfV9BU8V12Ez7ZsHGZpCQ== + dependencies: + "@leichtgewicht/ip-codec" "^2.0.1" + +dom-converter@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" + integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== + dependencies: + utila "~0.4" + +dom-serializer@^1.0.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30" + integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.0" + entities "^2.0.0" + +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + +domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + +domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" + integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== + dependencies: + domelementtype "^2.2.0" + +domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + +domutils@^2.5.2, domutils@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" + integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + +domutils@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" + integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + +dot-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" + integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + +dot-prop@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-6.0.1.tgz#fc26b3cf142b9e59b74dbd39ed66ce620c681083" + integrity sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA== + dependencies: + is-obj "^2.0.0" + +duplexer@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" + integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +electron-to-chromium@^1.4.431: + version "1.4.468" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.468.tgz#3cbf64ad67d9f12bfe69fefe5eb1935ec4f6ab7a" + integrity sha512-6M1qyhaJOt7rQtNti1lBA0GwclPH+oKCmsra/hkcWs5INLxfXXD/dtdnaKUYQu/pjOBP/8Osoe4mAcNvvzoFag== + +electron-to-chromium@^1.4.601: + version "1.4.616" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.616.tgz#4bddbc2c76e1e9dbf449ecd5da3d8119826ea4fb" + integrity sha512-1n7zWYh8eS0L9Uy+GskE0lkBUNK83cXTVJI0pU3mGprFsbfSdAc15VTFbo+A+Bq4pwstmL30AVcEU3Fo463lNg== + +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== + +emojilib@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/emojilib/-/emojilib-2.4.0.tgz#ac518a8bb0d5f76dda57289ccb2fdf9d39ae721e" + integrity sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw== + +emojis-list@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" + integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== + +emoticon@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/emoticon/-/emoticon-4.0.1.tgz#2d2bbbf231ce3a5909e185bbb64a9da703a1e749" + integrity sha512-dqx7eA9YaqyvYtUhJwT4rC1HIp82j5ybS1/vQ42ur+jBe17dJMwZE4+gvL1XadSFfxaPFFGt3Xsw+Y8akThDlw== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +end-of-stream@^1.1.0, end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +enhanced-resolve@^5.15.0: + version "5.15.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" + integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +entities@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== + +entities@^4.2.0, entities@^4.4.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + +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-module-lexer@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.3.0.tgz#6be9c9e0b4543a60cd166ff6f8b4e9dae0b0c16f" + integrity sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA== + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-goat@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-4.0.0.tgz#9424820331b510b0666b98f7873fe11ac4aa8081" + integrity sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg== + +escape-html@^1.0.3, escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +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== + +escape-string-regexp@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8" + integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== + +eslint-scope@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +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@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +estree-util-attach-comments@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz#344bde6a64c8a31d15231e5ee9e297566a691c2d" + integrity sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw== + dependencies: + "@types/estree" "^1.0.0" + +estree-util-build-jsx@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz#b6d0bced1dcc4f06f25cf0ceda2b2dcaf98168f1" + integrity sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ== + dependencies: + "@types/estree-jsx" "^1.0.0" + devlop "^1.0.0" + estree-util-is-identifier-name "^3.0.0" + estree-walker "^3.0.0" + +estree-util-is-identifier-name@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz#0b5ef4c4ff13508b34dcd01ecfa945f61fce5dbd" + integrity sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg== + +estree-util-to-js@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz#10a6fb924814e6abb62becf0d2bc4dea51d04f17" + integrity sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg== + dependencies: + "@types/estree-jsx" "^1.0.0" + astring "^1.8.0" + source-map "^0.7.0" + +estree-util-value-to-estree@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/estree-util-value-to-estree/-/estree-util-value-to-estree-3.0.1.tgz#0b7b5d6b6a4aaad5c60999ffbc265a985df98ac5" + integrity sha512-b2tdzTurEIbwRh+mKrEcaWfu1wgb8J1hVsgREg7FFiecWwK/PhO8X0kyc+0bIcKNtD4sqxIdNoRy6/p/TvECEA== + dependencies: + "@types/estree" "^1.0.0" + is-plain-obj "^4.0.0" + +estree-util-visit@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/estree-util-visit/-/estree-util-visit-2.0.0.tgz#13a9a9f40ff50ed0c022f831ddf4b58d05446feb" + integrity sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/unist" "^3.0.0" + +estree-walker@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" + integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== + dependencies: + "@types/estree" "^1.0.0" + +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== + +eta@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/eta/-/eta-2.2.0.tgz#eb8b5f8c4e8b6306561a455e62cd7492fe3a9b8a" + integrity sha512-UVQ72Rqjy/ZKQalzV5dCCJP80GrmPrMxh6NlNf+erV6ObL0ZFkhCstWRawS85z3smdr3d2wXPsZEY7rDPfGd2g== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +eval@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/eval/-/eval-0.1.8.tgz#2b903473b8cc1d1989b83a1e7923f883eb357f85" + integrity sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw== + dependencies: + "@types/node" "*" + require-like ">= 0.1.1" + +eventemitter3@^4.0.0: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +events@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +expand-template@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" + integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== + +express@^4.17.3: + version "4.18.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" + integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.1" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.5.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.2.0" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.7" + qs "6.11.0" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.18.0" + serve-static "1.15.0" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug== + dependencies: + is-extendable "^0.1.0" + +extend@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +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-fifo@^1.1.0, fast-fifo@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c" + integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ== + +fast-glob@^3.2.11, fast-glob@^3.2.9, fast-glob@^3.3.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" + integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== + 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-url-parser@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d" + integrity sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ== + dependencies: + punycode "^1.3.2" + +fastq@^1.6.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" + integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== + dependencies: + reusify "^1.0.4" + +fault@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fault/-/fault-2.0.1.tgz#d47ca9f37ca26e4bd38374a7c500b5a384755b6c" + integrity sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ== + dependencies: + format "^0.2.0" + +faye-websocket@^0.11.3: + version "0.11.4" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" + integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== + dependencies: + websocket-driver ">=0.5.1" + +feed@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/feed/-/feed-4.2.2.tgz#865783ef6ed12579e2c44bbef3c9113bc4956a7e" + integrity sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ== + dependencies: + xml-js "^1.6.11" + +file-loader@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" + integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== + dependencies: + loader-utils "^2.0.0" + schema-utils "^3.0.0" + +filesize@^8.0.6: + version "8.0.7" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-8.0.7.tgz#695e70d80f4e47012c132d57a059e80c6b580bd8" + integrity sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ== + +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" + +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + +find-cache-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-4.0.0.tgz#a30ee0448f81a3990708f6453633c733e2f6eec2" + integrity sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg== + dependencies: + common-path-prefix "^3.0.0" + pkg-dir "^7.0.0" + +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +find-up@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-6.3.0.tgz#2abab3d3280b2dc7ac10199ef324c4e002c8c790" + integrity sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw== + dependencies: + locate-path "^7.1.0" + path-exists "^5.0.0" + +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + +follow-redirects@^1.0.0: + version "1.15.2" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" + integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== + +fork-ts-checker-webpack-plugin@^6.5.0: + version "6.5.3" + resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz#eda2eff6e22476a2688d10661688c47f611b37f3" + integrity sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ== + dependencies: + "@babel/code-frame" "^7.8.3" + "@types/json-schema" "^7.0.5" + chalk "^4.1.0" + chokidar "^3.4.2" + cosmiconfig "^6.0.0" + deepmerge "^4.2.2" + fs-extra "^9.0.0" + glob "^7.1.6" + memfs "^3.1.2" + minimatch "^3.0.4" + schema-utils "2.7.0" + semver "^7.3.2" + tapable "^1.0.0" + +form-data-encoder@^2.1.2: + version "2.1.4" + resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-2.1.4.tgz#261ea35d2a70d48d30ec7a9603130fa5515e9cd5" + integrity sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw== + +format@^0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" + integrity sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww== + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fraction.js@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950" + integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA== + +fraction.js@^4.3.6: + version "4.3.7" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" + integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + +fs-extra@^11.1.1: + version "11.2.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b" + integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-extra@^9.0.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-monkey@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.4.tgz#ee8c1b53d3fe8bb7e5d2c5c5dfc0168afdd2f747" + integrity sha512-INM/fWAxMICjttnD0DX1rBvinKskj5G1w+oy/pnm9u/tSlnBrzFonJMcalKJ30P8RRsPzKcCG7Q8l0jx5Fh9YQ== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" + integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-proto "^1.0.1" + has-symbols "^1.0.3" + +get-own-enumerable-property-symbols@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" + integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== + +get-stream@^6.0.0, get-stream@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +github-from-package@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" + integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== + +github-slugger@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.5.0.tgz#17891bbc73232051474d68bd867a34625c955f7d" + integrity sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw== + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + 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-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + +glob@^7.0.0, glob@^7.1.3, glob@^7.1.6: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global-dirs@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.1.tgz#0c488971f066baceda21447aecb1a8b911d22485" + integrity sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA== + dependencies: + ini "2.0.0" + +global-modules@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" + integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== + dependencies: + global-prefix "^3.0.0" + +global-prefix@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" + integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== + dependencies: + ini "^1.3.5" + kind-of "^6.0.2" + which "^1.3.1" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globby@^11.0.1, globby@^11.0.4, globby@^11.1.0: + 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" + +globby@^13.1.1: + version "13.2.2" + resolved "https://registry.yarnpkg.com/globby/-/globby-13.2.2.tgz#63b90b1bf68619c2135475cbd4e71e66aa090592" + integrity sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w== + dependencies: + dir-glob "^3.0.1" + fast-glob "^3.3.0" + ignore "^5.2.4" + merge2 "^1.4.1" + slash "^4.0.0" + +got@^12.1.0: + version "12.6.1" + resolved "https://registry.yarnpkg.com/got/-/got-12.6.1.tgz#8869560d1383353204b5a9435f782df9c091f549" + integrity sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ== + dependencies: + "@sindresorhus/is" "^5.2.0" + "@szmarczak/http-timer" "^5.0.1" + cacheable-lookup "^7.0.0" + cacheable-request "^10.2.8" + decompress-response "^6.0.0" + form-data-encoder "^2.1.2" + get-stream "^6.0.1" + http2-wrapper "^2.1.10" + lowercase-keys "^3.0.0" + p-cancelable "^3.0.0" + responselike "^3.0.0" + +graceful-fs@4.2.10: + version "4.2.10" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +gray-matter@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-4.0.3.tgz#e893c064825de73ea1f5f7d88c7a9f7274288798" + integrity sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q== + dependencies: + js-yaml "^3.13.1" + kind-of "^6.0.2" + section-matter "^1.0.0" + strip-bom-string "^1.0.0" + +gzip-size@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" + integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q== + dependencies: + duplexer "^0.1.2" + +handle-thing@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" + integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" + integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== + dependencies: + get-intrinsic "^1.1.1" + +has-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" + integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== + +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-yarn@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-3.0.0.tgz#c3c21e559730d1d3b57e28af1f30d06fac38147d" + integrity sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA== + +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" + +hast-util-from-parse5@^8.0.0: + version "8.0.1" + resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz#654a5676a41211e14ee80d1b1758c399a0327651" + integrity sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ== + dependencies: + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + devlop "^1.0.0" + hastscript "^8.0.0" + property-information "^6.0.0" + vfile "^6.0.0" + vfile-location "^5.0.0" + web-namespaces "^2.0.0" + +hast-util-parse-selector@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz#352879fa86e25616036037dd8931fb5f34cb4a27" + integrity sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A== + dependencies: + "@types/hast" "^3.0.0" + +hast-util-raw@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/hast-util-raw/-/hast-util-raw-9.0.1.tgz#2ba8510e4ed2a1e541cde2a4ebb5c38ab4c82c2d" + integrity sha512-5m1gmba658Q+lO5uqL5YNGQWeh1MYWZbZmWrM5lncdcuiXuo5E2HT/CIOp0rLF8ksfSwiCVJ3twlgVRyTGThGA== + dependencies: + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + "@ungap/structured-clone" "^1.0.0" + hast-util-from-parse5 "^8.0.0" + hast-util-to-parse5 "^8.0.0" + html-void-elements "^3.0.0" + mdast-util-to-hast "^13.0.0" + parse5 "^7.0.0" + unist-util-position "^5.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + web-namespaces "^2.0.0" + zwitch "^2.0.0" + +hast-util-to-estree@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/hast-util-to-estree/-/hast-util-to-estree-3.1.0.tgz#f2afe5e869ddf0cf690c75f9fc699f3180b51b19" + integrity sha512-lfX5g6hqVh9kjS/B9E2gSkvHH4SZNiQFiqWS0x9fENzEl+8W12RqdRxX6d/Cwxi30tPQs3bIO+aolQJNp1bIyw== + dependencies: + "@types/estree" "^1.0.0" + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + comma-separated-tokens "^2.0.0" + devlop "^1.0.0" + estree-util-attach-comments "^3.0.0" + estree-util-is-identifier-name "^3.0.0" + hast-util-whitespace "^3.0.0" + mdast-util-mdx-expression "^2.0.0" + mdast-util-mdx-jsx "^3.0.0" + mdast-util-mdxjs-esm "^2.0.0" + property-information "^6.0.0" + space-separated-tokens "^2.0.0" + style-to-object "^0.4.0" + unist-util-position "^5.0.0" + zwitch "^2.0.0" + +hast-util-to-jsx-runtime@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.0.tgz#3ed27caf8dc175080117706bf7269404a0aa4f7c" + integrity sha512-H/y0+IWPdsLLS738P8tDnrQ8Z+dj12zQQ6WC11TIM21C8WFVoIxcqWXf2H3hiTVZjF1AWqoimGwrTWecWrnmRQ== + dependencies: + "@types/estree" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + comma-separated-tokens "^2.0.0" + devlop "^1.0.0" + estree-util-is-identifier-name "^3.0.0" + hast-util-whitespace "^3.0.0" + mdast-util-mdx-expression "^2.0.0" + mdast-util-mdx-jsx "^3.0.0" + mdast-util-mdxjs-esm "^2.0.0" + property-information "^6.0.0" + space-separated-tokens "^2.0.0" + style-to-object "^1.0.0" + unist-util-position "^5.0.0" + vfile-message "^4.0.0" + +hast-util-to-parse5@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz#477cd42d278d4f036bc2ea58586130f6f39ee6ed" + integrity sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw== + dependencies: + "@types/hast" "^3.0.0" + comma-separated-tokens "^2.0.0" + devlop "^1.0.0" + property-information "^6.0.0" + space-separated-tokens "^2.0.0" + web-namespaces "^2.0.0" + zwitch "^2.0.0" + +hast-util-whitespace@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz#7778ed9d3c92dd9e8c5c8f648a49c21fc51cb621" + integrity sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw== + dependencies: + "@types/hast" "^3.0.0" + +hastscript@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-8.0.0.tgz#4ef795ec8dee867101b9f23cc830d4baf4fd781a" + integrity sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw== + dependencies: + "@types/hast" "^3.0.0" + comma-separated-tokens "^2.0.0" + hast-util-parse-selector "^4.0.0" + property-information "^6.0.0" + space-separated-tokens "^2.0.0" + +he@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +history@^4.9.0: + version "4.10.1" + resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" + integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew== + dependencies: + "@babel/runtime" "^7.1.2" + loose-envify "^1.2.0" + resolve-pathname "^3.0.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + value-equal "^1.0.1" + +hoist-non-react-statics@^3.1.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + +hpack.js@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" + integrity sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ== + dependencies: + inherits "^2.0.1" + obuf "^1.0.0" + readable-stream "^2.0.1" + wbuf "^1.1.0" + +html-entities@^2.3.2: + version "2.4.0" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.4.0.tgz#edd0cee70402584c8c76cc2c0556db09d1f45061" + integrity sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ== + +html-escaper@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +html-minifier-terser@^6.0.2: + version "6.1.0" + resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#bfc818934cc07918f6b3669f5774ecdfd48f32ab" + integrity sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw== + dependencies: + camel-case "^4.1.2" + clean-css "^5.2.2" + commander "^8.3.0" + he "^1.2.0" + param-case "^3.0.4" + relateurl "^0.2.7" + terser "^5.10.0" + +html-minifier-terser@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz#18752e23a2f0ed4b0f550f217bb41693e975b942" + integrity sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA== + dependencies: + camel-case "^4.1.2" + clean-css "~5.3.2" + commander "^10.0.0" + entities "^4.4.0" + param-case "^3.0.4" + relateurl "^0.2.7" + terser "^5.15.1" + +html-tags@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce" + integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ== + +html-void-elements@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-3.0.0.tgz#fc9dbd84af9e747249034d4d62602def6517f1d7" + integrity sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg== + +html-webpack-plugin@^5.5.3: + version "5.6.0" + resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz#50a8fa6709245608cb00e811eacecb8e0d7b7ea0" + integrity sha512-iwaY4wzbe48AfKLZ/Cc8k0L+FKG6oSNRaZ8x5A/T/IVDGyXcbHncM9TdDa93wn0FsSm82FhTKW7f3vS61thXAw== + dependencies: + "@types/html-minifier-terser" "^6.0.0" + html-minifier-terser "^6.0.2" + lodash "^4.17.21" + pretty-error "^4.0.0" + tapable "^2.0.0" + +htmlparser2@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" + integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.0.0" + domutils "^2.5.2" + entities "^2.0.0" + +htmlparser2@^8.0.1: + version "8.0.2" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21" + integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.0.1" + entities "^4.4.0" + +http-cache-semantics@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" + integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== + +http-deceiver@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" + integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== + +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +http-errors@~1.6.2: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + +http-parser-js@>=0.5.1: + version "0.5.8" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3" + integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== + +http-proxy-middleware@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f" + integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw== + dependencies: + "@types/http-proxy" "^1.17.8" + http-proxy "^1.18.1" + is-glob "^4.0.1" + is-plain-obj "^3.0.0" + micromatch "^4.0.2" + +http-proxy@^1.18.1: + version "1.18.1" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== + dependencies: + eventemitter3 "^4.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + +http2-wrapper@^2.1.10: + version "2.2.1" + resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-2.2.1.tgz#310968153dcdedb160d8b72114363ef5fce1f64a" + integrity sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ== + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.2.0" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +icss-utils@^5.0.0, icss-utils@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" + integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== + +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore@^5.2.0, ignore@^5.2.4: + version "5.2.4" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" + integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== + +image-size@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/image-size/-/image-size-1.0.2.tgz#d778b6d0ab75b2737c1556dd631652eb963bc486" + integrity sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg== + dependencies: + queue "6.0.2" + +immer@^9.0.7: + version "9.0.21" + resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176" + integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA== + +import-fresh@^3.1.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" + +import-lazy@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153" + integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw== + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +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== + +infima@0.2.0-alpha.43: + version "0.2.0-alpha.43" + resolved "https://registry.yarnpkg.com/infima/-/infima-0.2.0-alpha.43.tgz#f7aa1d7b30b6c08afef441c726bac6150228cbe0" + integrity sha512-2uw57LvUqW0rK/SWYnd/2rRfxNA5DDNOh33jxF7fy46VWoNhGxiUQyVZHbBMjQ33mQem0cjdDVwgWVAmlRfgyQ== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, 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== + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== + +ini@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" + integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== + +ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +inline-style-parser@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1" + integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q== + +inline-style-parser@0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.2.2.tgz#d498b4e6de0373458fc610ff793f6b14ebf45633" + integrity sha512-EcKzdTHVe8wFVOGEYXiW9WmJXPjqi1T+234YpJr98RiFYKHV3cdy1+3mkTE+KHTHxFFLH51SfaGOoUdW+v7ViQ== + +interpret@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" + integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== + +invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +ipaddr.js@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.1.0.tgz#2119bc447ff8c257753b196fc5f1ce08a4cdf39f" + integrity sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ== + +is-alphabetical@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-2.0.1.tgz#01072053ea7c1036df3c7d19a6daaec7f19e789b" + integrity sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ== + +is-alphanumerical@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz#7c03fbe96e3e931113e57f964b0a368cc2dfd875" + integrity sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw== + dependencies: + is-alphabetical "^2.0.0" + is-decimal "^2.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-ci@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.1.tgz#db6ecbed1bd659c43dac0f45661e7674103d1867" + integrity sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ== + dependencies: + ci-info "^3.2.0" + +is-core-module@^2.11.0: + version "2.12.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.1.tgz#0c0b6885b6f80011c71541ce15c8d66cf5a4f9fd" + integrity sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg== + dependencies: + has "^1.0.3" + +is-decimal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-2.0.1.tgz#9469d2dc190d0214fd87d78b78caecc0cc14eef7" + integrity sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A== + +is-docker@^2.0.0, is-docker@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + +is-extendable@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-hexadecimal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz#86b5bf668fca307498d319dfc03289d781a90027" + integrity sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg== + +is-installed-globally@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" + integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== + dependencies: + global-dirs "^3.0.0" + is-path-inside "^3.0.2" + +is-npm@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-6.0.0.tgz#b59e75e8915543ca5d881ecff864077cba095261" + integrity sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ== + +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-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" + integrity sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg== + +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + +is-path-cwd@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" + integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== + +is-path-inside@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-plain-obj@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" + integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== + +is-plain-obj@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0" + integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg== + +is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + +is-reference@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-3.0.2.tgz#154747a01f45cd962404ee89d43837af2cba247c" + integrity sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg== + dependencies: + "@types/estree" "*" + +is-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" + integrity sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA== + +is-root@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c" + integrity sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-typedarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +is-yarn-global@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.4.1.tgz#b312d902b313f81e4eaf98b6361ba2b45cd694bb" + integrity sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ== + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== + +jest-util@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.6.1.tgz#c9e29a87a6edbf1e39e6dee2b4689b8a146679cb" + integrity sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg== + dependencies: + "@jest/types" "^29.6.1" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-worker@^27.4.5: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest-worker@^29.1.2: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.6.1.tgz#64b015f0e985ef3a8ad049b61fe92b3db74a5319" + integrity sha512-U+Wrbca7S8ZAxAe9L6nb6g8kPdia5hj32Puu5iOqBCMTMWFHXuK6dOV2IFrpedbTV8fjMFLdWNttQTBL6u2MRA== + dependencies: + "@types/node" "*" + jest-util "^29.6.1" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jiti@^1.18.2: + version "1.19.1" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.19.1.tgz#fa99e4b76a23053e0e7cde098efe1704a14c16f1" + integrity sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg== + +jiti@^1.20.0: + version "1.21.0" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d" + integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q== + +joi@^17.9.2: + version "17.11.0" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.11.0.tgz#aa9da753578ec7720e6f0ca2c7046996ed04fc1a" + integrity sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ== + dependencies: + "@hapi/hoek" "^9.0.0" + "@hapi/topo" "^5.0.0" + "@sideway/address" "^4.1.3" + "@sideway/formula" "^3.0.1" + "@sideway/pinpoint" "^2.0.0" + +"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@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +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" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: + 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-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json5@^2.1.2, json5@^2.2.2, json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +latest-version@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-7.0.0.tgz#843201591ea81a4d404932eeb61240fe04e9e5da" + integrity sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg== + dependencies: + package-json "^8.1.0" + +launch-editor@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.6.0.tgz#4c0c1a6ac126c572bd9ff9a30da1d2cae66defd7" + integrity sha512-JpDCcQnyAAzZZaZ7vEiSqL690w7dAEyLao+KC96zBplnYbJS7TYNjvM3M7y3dGz+v7aIsJk3hllWuc0kWAjyRQ== + dependencies: + picocolors "^1.0.0" + shell-quote "^1.7.3" + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +lilconfig@^2.0.3: + version "2.1.0" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" + integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== + +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== + +loader-runner@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" + integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== + +loader-utils@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c" + integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^2.1.2" + +loader-utils@^3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-3.2.1.tgz#4fb104b599daafd82ef3e1a41fb9265f87e1f576" + integrity sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw== + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +locate-path@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-7.2.0.tgz#69cb1779bd90b35ab1e771e1f2f89a202c2a8a8a" + integrity sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA== + dependencies: + p-locate "^6.0.0" + +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== + +lodash@^4.17.20, lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +longest-streak@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4" + integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g== + +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, 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" + +lower-case@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" + integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== + dependencies: + tslib "^2.0.3" + +lowercase-keys@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-3.0.0.tgz#c5e7d442e37ead247ae9db117a9d0a467c89d4f2" + integrity sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +markdown-extensions@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/markdown-extensions/-/markdown-extensions-2.0.0.tgz#34bebc83e9938cae16e0e017e4a9814a8330d3c4" + integrity sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q== + +markdown-table@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.3.tgz#e6331d30e493127e031dd385488b5bd326e4a6bd" + integrity sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw== + +mdast-util-directive@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-directive/-/mdast-util-directive-3.0.0.tgz#3fb1764e705bbdf0afb0d3f889e4404c3e82561f" + integrity sha512-JUpYOqKI4mM3sZcNxmF/ox04XYFFkNwr0CFlrQIkCwbvH0xzMCqkMqAde9wRd80VAhaUrwFwKm2nxretdT1h7Q== + dependencies: + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + parse-entities "^4.0.0" + stringify-entities "^4.0.0" + unist-util-visit-parents "^6.0.0" + +mdast-util-find-and-replace@^3.0.0, mdast-util-find-and-replace@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.1.tgz#a6fc7b62f0994e973490e45262e4bc07607b04e0" + integrity sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA== + dependencies: + "@types/mdast" "^4.0.0" + escape-string-regexp "^5.0.0" + unist-util-is "^6.0.0" + unist-util-visit-parents "^6.0.0" + +mdast-util-from-markdown@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.0.tgz#52f14815ec291ed061f2922fd14d6689c810cb88" + integrity sha512-n7MTOr/z+8NAX/wmhhDji8O3bRvPTV/U0oTCaZJkjhPSKTPhS3xufVhKGF8s1pJ7Ox4QgoIU7KHseh09S+9rTA== + dependencies: + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + mdast-util-to-string "^4.0.0" + micromark "^4.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-decode-string "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + unist-util-stringify-position "^4.0.0" + +mdast-util-frontmatter@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz#f5f929eb1eb36c8a7737475c7eb438261f964ee8" + integrity sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA== + dependencies: + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + escape-string-regexp "^5.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + micromark-extension-frontmatter "^2.0.0" + +mdast-util-gfm-autolink-literal@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.0.tgz#5baf35407421310a08e68c15e5d8821e8898ba2a" + integrity sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg== + dependencies: + "@types/mdast" "^4.0.0" + ccount "^2.0.0" + devlop "^1.0.0" + mdast-util-find-and-replace "^3.0.0" + micromark-util-character "^2.0.0" + +mdast-util-gfm-footnote@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.0.0.tgz#25a1753c7d16db8bfd53cd84fe50562bd1e6d6a9" + integrity sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ== + dependencies: + "@types/mdast" "^4.0.0" + devlop "^1.1.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + +mdast-util-gfm-strikethrough@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz#d44ef9e8ed283ac8c1165ab0d0dfd058c2764c16" + integrity sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-gfm-table@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz#7a435fb6223a72b0862b33afbd712b6dae878d38" + integrity sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg== + dependencies: + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + markdown-table "^3.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-gfm-task-list-item@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz#e68095d2f8a4303ef24094ab642e1047b991a936" + integrity sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ== + dependencies: + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-gfm@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm/-/mdast-util-gfm-3.0.0.tgz#3f2aecc879785c3cb6a81ff3a243dc11eca61095" + integrity sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw== + dependencies: + mdast-util-from-markdown "^2.0.0" + mdast-util-gfm-autolink-literal "^2.0.0" + mdast-util-gfm-footnote "^2.0.0" + mdast-util-gfm-strikethrough "^2.0.0" + mdast-util-gfm-table "^2.0.0" + mdast-util-gfm-task-list-item "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-mdx-expression@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.0.tgz#4968b73724d320a379110d853e943a501bfd9d87" + integrity sha512-fGCu8eWdKUKNu5mohVGkhBXCXGnOTLuFqOvGMvdikr+J1w7lDJgxThOKpwRWzzbyXAU2hhSwsmssOY4yTokluw== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-mdx-jsx@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.0.0.tgz#f73631fa5bb7a36712ff1e9cedec0cafed03401c" + integrity sha512-XZuPPzQNBPAlaqsTTgRrcJnyFbSOBovSadFgbFu8SnuNgm+6Bdx1K+IWoitsmj6Lq6MNtI+ytOqwN70n//NaBA== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + ccount "^2.0.0" + devlop "^1.1.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + parse-entities "^4.0.0" + stringify-entities "^4.0.0" + unist-util-remove-position "^5.0.0" + unist-util-stringify-position "^4.0.0" + vfile-message "^4.0.0" + +mdast-util-mdx@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz#792f9cf0361b46bee1fdf1ef36beac424a099c41" + integrity sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w== + dependencies: + mdast-util-from-markdown "^2.0.0" + mdast-util-mdx-expression "^2.0.0" + mdast-util-mdx-jsx "^3.0.0" + mdast-util-mdxjs-esm "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-mdxjs-esm@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz#019cfbe757ad62dd557db35a695e7314bcc9fa97" + integrity sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-phrasing@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-phrasing/-/mdast-util-phrasing-4.0.0.tgz#468cbbb277375523de807248b8ad969feb02a5c7" + integrity sha512-xadSsJayQIucJ9n053dfQwVu1kuXg7jCTdYsMK8rqzKZh52nLfSH/k0sAxE0u+pj/zKZX+o5wB+ML5mRayOxFA== + dependencies: + "@types/mdast" "^4.0.0" + unist-util-is "^6.0.0" + +mdast-util-to-hast@^13.0.0: + version "13.0.2" + resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-13.0.2.tgz#74c0a9f014bb2340cae6118f6fccd75467792be7" + integrity sha512-U5I+500EOOw9e3ZrclN3Is3fRpw8c19SMyNZlZ2IS+7vLsNzb2Om11VpIVOR+/0137GhZsFEF6YiKD5+0Hr2Og== + dependencies: + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + "@ungap/structured-clone" "^1.0.0" + devlop "^1.0.0" + micromark-util-sanitize-uri "^2.0.0" + trim-lines "^3.0.0" + unist-util-position "^5.0.0" + unist-util-visit "^5.0.0" + +mdast-util-to-markdown@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz#9813f1d6e0cdaac7c244ec8c6dabfdb2102ea2b4" + integrity sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ== + dependencies: + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + longest-streak "^3.0.0" + mdast-util-phrasing "^4.0.0" + mdast-util-to-string "^4.0.0" + micromark-util-decode-string "^2.0.0" + unist-util-visit "^5.0.0" + zwitch "^2.0.0" + +mdast-util-to-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz#7a5121475556a04e7eddeb67b264aae79d312814" + integrity sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg== + dependencies: + "@types/mdast" "^4.0.0" + +mdn-data@2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" + integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +memfs@^3.1.2, memfs@^3.4.3: + version "3.6.0" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6" + integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ== + dependencies: + fs-monkey "^1.0.4" + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +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== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +micromark-core-commonmark@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.0.tgz#50740201f0ee78c12a675bf3e68ffebc0bf931a3" + integrity sha512-jThOz/pVmAYUtkroV3D5c1osFXAMv9e0ypGDOIZuCeAe91/sD6BoE2Sjzt30yuXtwOYUmySOhMas/PVyh02itA== + dependencies: + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + micromark-factory-destination "^2.0.0" + micromark-factory-label "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-factory-title "^2.0.0" + micromark-factory-whitespace "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-classify-character "^2.0.0" + micromark-util-html-tag-name "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-subtokenize "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-directive@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-directive/-/micromark-extension-directive-3.0.0.tgz#527869de497a6de9024138479091bc885dae076b" + integrity sha512-61OI07qpQrERc+0wEysLHMvoiO3s2R56x5u7glHq2Yqq6EHbH4dW25G9GfDdGCDYqA21KE6DWgNSzxSwHc2hSg== + dependencies: + devlop "^1.0.0" + micromark-factory-space "^2.0.0" + micromark-factory-whitespace "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + parse-entities "^4.0.0" + +micromark-extension-frontmatter@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz#651c52ffa5d7a8eeed687c513cd869885882d67a" + integrity sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg== + dependencies: + fault "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm-autolink-literal@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.0.0.tgz#f1e50b42e67d441528f39a67133eddde2bbabfd9" + integrity sha512-rTHfnpt/Q7dEAK1Y5ii0W8bhfJlVJFnJMHIPisfPK3gpVNuOP0VnRl96+YJ3RYWV/P4gFeQoGKNlT3RhuvpqAg== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-sanitize-uri "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm-footnote@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.0.0.tgz#91afad310065a94b636ab1e9dab2c60d1aab953c" + integrity sha512-6Rzu0CYRKDv3BfLAUnZsSlzx3ak6HAoI85KTiijuKIz5UxZxbUI+pD6oHgw+6UtQuiRwnGRhzMmPRv4smcz0fg== + dependencies: + devlop "^1.0.0" + micromark-core-commonmark "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-sanitize-uri "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm-strikethrough@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.0.0.tgz#6917db8e320da70e39ffbf97abdbff83e6783e61" + integrity sha512-c3BR1ClMp5fxxmwP6AoOY2fXO9U8uFMKs4ADD66ahLTNcwzSCyRVU4k7LPV5Nxo/VJiR4TdzxRQY2v3qIUceCw== + dependencies: + devlop "^1.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-classify-character "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm-table@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.0.0.tgz#2cf3fe352d9e089b7ef5fff003bdfe0da29649b7" + integrity sha512-PoHlhypg1ItIucOaHmKE8fbin3vTLpDOUg8KAr8gRCF1MOZI9Nquq2i/44wFvviM4WuxJzc3demT8Y3dkfvYrw== + dependencies: + devlop "^1.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm-tagfilter@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz#f26d8a7807b5985fba13cf61465b58ca5ff7dc57" + integrity sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg== + dependencies: + micromark-util-types "^2.0.0" + +micromark-extension-gfm-task-list-item@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.0.1.tgz#ee8b208f1ced1eb9fb11c19a23666e59d86d4838" + integrity sha512-cY5PzGcnULaN5O7T+cOzfMoHjBW7j+T9D2sucA5d/KbsBTPcYdebm9zUd9zzdgJGCwahV+/W78Z3nbulBYVbTw== + dependencies: + devlop "^1.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz#3e13376ab95dd7a5cfd0e29560dfe999657b3c5b" + integrity sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w== + dependencies: + micromark-extension-gfm-autolink-literal "^2.0.0" + micromark-extension-gfm-footnote "^2.0.0" + micromark-extension-gfm-strikethrough "^2.0.0" + micromark-extension-gfm-table "^2.0.0" + micromark-extension-gfm-tagfilter "^2.0.0" + micromark-extension-gfm-task-list-item "^2.0.0" + micromark-util-combine-extensions "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-mdx-expression@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.0.tgz#1407b9ce69916cf5e03a196ad9586889df25302a" + integrity sha512-sI0nwhUDz97xyzqJAbHQhp5TfaxEvZZZ2JDqUo+7NvyIYG6BZ5CPPqj2ogUoPJlmXHBnyZUzISg9+oUmU6tUjQ== + dependencies: + "@types/estree" "^1.0.0" + devlop "^1.0.0" + micromark-factory-mdx-expression "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-events-to-acorn "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-mdx-jsx@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.0.tgz#4aba0797c25efb2366a3fd2d367c6b1c1159f4f5" + integrity sha512-uvhhss8OGuzR4/N17L1JwvmJIpPhAd8oByMawEKx6NVdBCbesjH4t+vjEp3ZXft9DwvlKSD07fCeI44/N0Vf2w== + dependencies: + "@types/acorn" "^4.0.0" + "@types/estree" "^1.0.0" + devlop "^1.0.0" + estree-util-is-identifier-name "^3.0.0" + micromark-factory-mdx-expression "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + vfile-message "^4.0.0" + +micromark-extension-mdx-md@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz#1d252881ea35d74698423ab44917e1f5b197b92d" + integrity sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ== + dependencies: + micromark-util-types "^2.0.0" + +micromark-extension-mdxjs-esm@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz#de21b2b045fd2059bd00d36746081de38390d54a" + integrity sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A== + dependencies: + "@types/estree" "^1.0.0" + devlop "^1.0.0" + micromark-core-commonmark "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-events-to-acorn "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + unist-util-position-from-estree "^2.0.0" + vfile-message "^4.0.0" + +micromark-extension-mdxjs@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz#b5a2e0ed449288f3f6f6c544358159557549de18" + integrity sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ== + dependencies: + acorn "^8.0.0" + acorn-jsx "^5.0.0" + micromark-extension-mdx-expression "^3.0.0" + micromark-extension-mdx-jsx "^3.0.0" + micromark-extension-mdx-md "^2.0.0" + micromark-extension-mdxjs-esm "^3.0.0" + micromark-util-combine-extensions "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-destination@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz#857c94debd2c873cba34e0445ab26b74f6a6ec07" + integrity sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-label@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-factory-label/-/micromark-factory-label-2.0.0.tgz#17c5c2e66ce39ad6f4fc4cbf40d972f9096f726a" + integrity sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw== + dependencies: + devlop "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-mdx-expression@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.1.tgz#f2a9724ce174f1751173beb2c1f88062d3373b1b" + integrity sha512-F0ccWIUHRLRrYp5TC9ZYXmZo+p2AM13ggbsW4T0b5CRKP8KHVRB8t4pwtBgTxtjRmwrK0Irwm7vs2JOZabHZfg== + dependencies: + "@types/estree" "^1.0.0" + devlop "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-events-to-acorn "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + unist-util-position-from-estree "^2.0.0" + vfile-message "^4.0.0" + +micromark-factory-space@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz#c8f40b0640a0150751d3345ed885a080b0d15faf" + integrity sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ== + dependencies: + micromark-util-character "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-factory-space@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz#5e7afd5929c23b96566d0e1ae018ae4fcf81d030" + integrity sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-title@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-factory-title/-/micromark-factory-title-2.0.0.tgz#726140fc77892af524705d689e1cf06c8a83ea95" + integrity sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A== + dependencies: + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-whitespace@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.0.tgz#9e92eb0f5468083381f923d9653632b3cfb5f763" + integrity sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA== + dependencies: + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-character@^1.0.0, micromark-util-character@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-1.2.0.tgz#4fedaa3646db249bc58caeb000eb3549a8ca5dcc" + integrity sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg== + dependencies: + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-util-character@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-2.0.1.tgz#52b824c2e2633b6fb33399d2ec78ee2a90d6b298" + integrity sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw== + dependencies: + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-chunked@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-chunked/-/micromark-util-chunked-2.0.0.tgz#e51f4db85fb203a79dbfef23fd41b2f03dc2ef89" + integrity sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-classify-character@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-classify-character/-/micromark-util-classify-character-2.0.0.tgz#8c7537c20d0750b12df31f86e976d1d951165f34" + integrity sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-combine-extensions@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.0.tgz#75d6ab65c58b7403616db8d6b31315013bfb7ee5" + integrity sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ== + dependencies: + micromark-util-chunked "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-decode-numeric-character-reference@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.1.tgz#2698bbb38f2a9ba6310e359f99fcb2b35a0d2bd5" + integrity sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-decode-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-decode-string/-/micromark-util-decode-string-2.0.0.tgz#7dfa3a63c45aecaa17824e656bcdb01f9737154a" + integrity sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA== + dependencies: + decode-named-character-reference "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-symbol "^2.0.0" + +micromark-util-encode@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz#0921ac7953dc3f1fd281e3d1932decfdb9382ab1" + integrity sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA== + +micromark-util-events-to-acorn@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.2.tgz#4275834f5453c088bd29cd72dfbf80e3327cec07" + integrity sha512-Fk+xmBrOv9QZnEDguL9OI9/NQQp6Hz4FuQ4YmCb/5V7+9eAh1s6AYSvL20kHkD67YIg7EpE54TiSlcsf3vyZgA== + dependencies: + "@types/acorn" "^4.0.0" + "@types/estree" "^1.0.0" + "@types/unist" "^3.0.0" + devlop "^1.0.0" + estree-util-visit "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + vfile-message "^4.0.0" + +micromark-util-html-tag-name@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.0.tgz#ae34b01cbe063363847670284c6255bb12138ec4" + integrity sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw== + +micromark-util-normalize-identifier@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.0.tgz#91f9a4e65fe66cc80c53b35b0254ad67aa431d8b" + integrity sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-resolve-all@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.0.tgz#189656e7e1a53d0c86a38a652b284a252389f364" + integrity sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA== + dependencies: + micromark-util-types "^2.0.0" + +micromark-util-sanitize-uri@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz#ec8fbf0258e9e6d8f13d9e4770f9be64342673de" + integrity sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-encode "^2.0.0" + micromark-util-symbol "^2.0.0" + +micromark-util-subtokenize@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.0.tgz#9f412442d77e0c5789ffdf42377fa8a2bcbdf581" + integrity sha512-vc93L1t+gpR3p8jxeVdaYlbV2jTYteDje19rNSS/H5dlhxUYll5Fy6vJ2cDwP8RnsXi818yGty1ayP55y3W6fg== + dependencies: + devlop "^1.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-symbol@^1.0.0, micromark-util-symbol@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz#813cd17837bdb912d069a12ebe3a44b6f7063142" + integrity sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag== + +micromark-util-symbol@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz#12225c8f95edf8b17254e47080ce0862d5db8044" + integrity sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw== + +micromark-util-types@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-1.1.0.tgz#e6676a8cae0bb86a2171c498167971886cb7e283" + integrity sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg== + +micromark-util-types@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.0.tgz#63b4b7ffeb35d3ecf50d1ca20e68fc7caa36d95e" + integrity sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w== + +micromark@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/micromark/-/micromark-4.0.0.tgz#84746a249ebd904d9658cfabc1e8e5f32cbc6249" + integrity sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ== + dependencies: + "@types/debug" "^4.0.0" + debug "^4.0.0" + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + micromark-core-commonmark "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-combine-extensions "^2.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-encode "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-sanitize-uri "^2.0.0" + micromark-util-subtokenize "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-db@~1.33.0: + version "1.33.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" + integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ== + +mime-types@2.1.18: + version "2.1.18" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" + integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ== + dependencies: + mime-db "~1.33.0" + +mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + +mimic-response@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-4.0.0.tgz#35468b19e7c75d10f5165ea25e75a5ceea7cf70f" + integrity sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg== + +mini-css-extract-plugin@^2.7.6: + version "2.7.6" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.6.tgz#282a3d38863fddcd2e0c220aaed5b90bc156564d" + integrity sha512-Qk7HcgaPkGG6eD77mLvZS1nmxlao3j+9PkrT9Uc7HAE1id3F41+DdBRYRYkbyfNRGzm8/YWtzhw7nVPmwhqTQw== + dependencies: + schema-utils "^4.0.0" + +minimalistic-assert@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimatch@3.1.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.0, minimist@^1.2.3: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + +mrmime@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-2.0.0.tgz#151082a6e06e59a9a39b46b3e14d5cfe92b3abb4" + integrity sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +multicast-dns@^7.2.5: + version "7.2.5" + resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced" + integrity sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg== + dependencies: + dns-packet "^5.2.2" + thunky "^1.0.2" + +nanoid@^3.3.6: + version "3.3.6" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" + integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== + +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + +napi-build-utils@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" + integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +no-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" + integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== + dependencies: + lower-case "^2.0.2" + tslib "^2.0.3" + +node-abi@^3.3.0: + version "3.45.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.45.0.tgz#f568f163a3bfca5aacfce1fbeee1fa2cc98441f5" + integrity sha512-iwXuFrMAcFVi/ZoZiqq8BzAdsLw9kxDfTC0HMyjXfSL/6CSDAGD5UmR7azrAgWV1zKYq7dUUMj4owusBWKLsiQ== + dependencies: + semver "^7.3.5" + +node-addon-api@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-6.1.0.tgz#ac8470034e58e67d0c6f1204a18ae6995d9c0d76" + integrity sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA== + +node-emoji@^2.1.0: + version "2.1.3" + resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-2.1.3.tgz#93cfabb5cc7c3653aa52f29d6ffb7927d8047c06" + integrity sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA== + dependencies: + "@sindresorhus/is" "^4.6.0" + char-regex "^1.0.2" + emojilib "^2.4.0" + skin-tone "^2.0.0" + +node-forge@^1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" + integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== + +node-releases@^2.0.12: + version "2.0.13" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" + integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== + +node-releases@^2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" + integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== + +normalize-url@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== + +normalize-url@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-8.0.0.tgz#593dbd284f743e8dcf6a5ddf8fadff149c82701a" + integrity sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +nprogress@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/nprogress/-/nprogress-0.2.0.tgz#cb8f34c53213d895723fcbab907e9422adbcafb1" + integrity sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA== + +nth-check@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== + dependencies: + boolbase "^1.0.0" + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-inspect@^1.9.0: + version "1.12.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" + integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== + +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.0: + version "4.1.4" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" + integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + has-symbols "^1.0.3" + object-keys "^1.1.1" + +obuf@^1.0.0, obuf@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" + integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== + +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +on-headers@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +open@^8.0.9, open@^8.4.0: + version "8.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" + integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== + dependencies: + define-lazy-prop "^2.0.0" + is-docker "^2.1.1" + is-wsl "^2.2.0" + +opener@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" + integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== + +p-cancelable@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-3.0.0.tgz#63826694b54d61ca1c20ebcb6d3ecf5e14cd8050" + integrity sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw== + +p-limit@^2.0.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-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-limit@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-4.0.0.tgz#914af6544ed32bfa54670b061cafcbd04984b644" + integrity sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ== + dependencies: + yocto-queue "^1.0.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-locate@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-6.0.0.tgz#3da9a49d4934b901089dca3302fa65dc5a05c04f" + integrity sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw== + dependencies: + p-limit "^4.0.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-retry@^4.5.0: + version "4.6.2" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16" + integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== + dependencies: + "@types/retry" "0.12.0" + retry "^0.13.1" + +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== + +package-json@^8.1.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/package-json/-/package-json-8.1.1.tgz#3e9948e43df40d1e8e78a85485f1070bf8f03dc8" + integrity sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA== + dependencies: + got "^12.1.0" + registry-auth-token "^5.0.1" + registry-url "^6.0.0" + semver "^7.3.7" + +param-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5" + integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A== + dependencies: + dot-case "^3.0.4" + tslib "^2.0.3" + +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-entities@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-4.0.1.tgz#4e2a01111fb1c986549b944af39eeda258fc9e4e" + integrity sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w== + dependencies: + "@types/unist" "^2.0.0" + character-entities "^2.0.0" + character-entities-legacy "^3.0.0" + character-reference-invalid "^2.0.0" + decode-named-character-reference "^1.0.0" + is-alphanumerical "^2.0.0" + is-decimal "^2.0.0" + is-hexadecimal "^2.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" + +parse-numeric-range@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz#7c63b61190d61e4d53a1197f0c83c47bb670ffa3" + integrity sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ== + +parse5-htmlparser2-tree-adapter@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz#23c2cc233bcf09bb7beba8b8a69d46b08c62c2f1" + integrity sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g== + dependencies: + domhandler "^5.0.2" + parse5 "^7.0.0" + +parse5@^7.0.0: + version "7.1.2" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" + integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== + dependencies: + entities "^4.4.0" + +parseurl@~1.3.2, parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +pascal-case@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb" + integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ== + +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-exists@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-5.0.0.tgz#a6aad9489200b21fab31e49cf09277e5116fb9e7" + integrity sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ== + +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 sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-is-inside@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + integrity sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.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-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== + +path-to-regexp@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.2.1.tgz#90b617025a16381a879bc82a38d4e8bdeb2bcf45" + integrity sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ== + +path-to-regexp@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" + integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== + dependencies: + isarray "0.0.1" + +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== + +periscopic@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/periscopic/-/periscopic-3.1.0.tgz#7e9037bf51c5855bd33b48928828db4afa79d97a" + integrity sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw== + dependencies: + "@types/estree" "^1.0.0" + estree-walker "^3.0.0" + is-reference "^3.0.0" + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pkg-dir@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-7.0.0.tgz#8f0c08d6df4476756c5ff29b3282d0bab7517d11" + integrity sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA== + dependencies: + find-up "^6.3.0" + +pkg-up@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" + integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== + dependencies: + find-up "^3.0.0" + +postcss-calc@^8.2.3: + version "8.2.4" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.2.4.tgz#77b9c29bfcbe8a07ff6693dc87050828889739a5" + integrity sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q== + dependencies: + postcss-selector-parser "^6.0.9" + postcss-value-parser "^4.2.0" + +postcss-colormin@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.3.1.tgz#86c27c26ed6ba00d96c79e08f3ffb418d1d1988f" + integrity sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ== + dependencies: + browserslist "^4.21.4" + caniuse-api "^3.0.0" + colord "^2.9.1" + postcss-value-parser "^4.2.0" + +postcss-convert-values@^5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz#04998bb9ba6b65aa31035d669a6af342c5f9d393" + integrity sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA== + dependencies: + browserslist "^4.21.4" + postcss-value-parser "^4.2.0" + +postcss-discard-comments@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz#8df5e81d2925af2780075840c1526f0660e53696" + integrity sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ== + +postcss-discard-duplicates@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz#9eb4fe8456706a4eebd6d3b7b777d07bad03e848" + integrity sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw== + +postcss-discard-empty@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz#e57762343ff7f503fe53fca553d18d7f0c369c6c" + integrity sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A== + +postcss-discard-overridden@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz#7e8c5b53325747e9d90131bb88635282fb4a276e" + integrity sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw== + +postcss-discard-unused@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-5.1.0.tgz#8974e9b143d887677304e558c1166d3762501142" + integrity sha512-KwLWymI9hbwXmJa0dkrzpRbSJEh0vVUd7r8t0yOGPcfKzyJJxFM8kLyC5Ev9avji6nY95pOp1W6HqIrfT+0VGw== + dependencies: + postcss-selector-parser "^6.0.5" + +postcss-loader@^7.3.3: + version "7.3.3" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-7.3.3.tgz#6da03e71a918ef49df1bb4be4c80401df8e249dd" + integrity sha512-YgO/yhtevGO/vJePCQmTxiaEwER94LABZN0ZMT4A0vsak9TpO+RvKRs7EmJ8peIlB9xfXCsS7M8LjqncsUZ5HA== + dependencies: + cosmiconfig "^8.2.0" + jiti "^1.18.2" + semver "^7.3.8" + +postcss-merge-idents@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-5.1.1.tgz#7753817c2e0b75d0853b56f78a89771e15ca04a1" + integrity sha512-pCijL1TREiCoog5nQp7wUe+TUonA2tC2sQ54UGeMmryK3UFGIYKqDyjnqd6RcuI4znFn9hWSLNN8xKE/vWcUQw== + dependencies: + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" + +postcss-merge-longhand@^5.1.7: + version "5.1.7" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz#24a1bdf402d9ef0e70f568f39bdc0344d568fb16" + integrity sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ== + dependencies: + postcss-value-parser "^4.2.0" + stylehacks "^5.1.1" + +postcss-merge-rules@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz#2f26fa5cacb75b1402e213789f6766ae5e40313c" + integrity sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g== + dependencies: + browserslist "^4.21.4" + caniuse-api "^3.0.0" + cssnano-utils "^3.1.0" + postcss-selector-parser "^6.0.5" + +postcss-minify-font-values@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz#f1df0014a726083d260d3bd85d7385fb89d1f01b" + integrity sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-minify-gradients@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz#f1fe1b4f498134a5068240c2f25d46fcd236ba2c" + integrity sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw== + dependencies: + colord "^2.9.1" + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" + +postcss-minify-params@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz#c06a6c787128b3208b38c9364cfc40c8aa5d7352" + integrity sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw== + dependencies: + browserslist "^4.21.4" + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" + +postcss-minify-selectors@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz#d4e7e6b46147b8117ea9325a915a801d5fe656c6" + integrity sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg== + dependencies: + postcss-selector-parser "^6.0.5" + +postcss-modules-extract-imports@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" + integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== + +postcss-modules-local-by-default@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz#b08eb4f083050708998ba2c6061b50c2870ca524" + integrity sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA== + dependencies: + icss-utils "^5.0.0" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.1.0" + +postcss-modules-scope@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" + integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== + dependencies: + postcss-selector-parser "^6.0.4" + +postcss-modules-values@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" + integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== + dependencies: + icss-utils "^5.0.0" + +postcss-normalize-charset@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz#9302de0b29094b52c259e9b2cf8dc0879879f0ed" + integrity sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg== + +postcss-normalize-display-values@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz#72abbae58081960e9edd7200fcf21ab8325c3da8" + integrity sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-positions@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz#ef97279d894087b59325b45c47f1e863daefbb92" + integrity sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-repeat-style@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz#e9eb96805204f4766df66fd09ed2e13545420fb2" + integrity sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-string@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz#411961169e07308c82c1f8c55f3e8a337757e228" + integrity sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-timing-functions@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz#d5614410f8f0b2388e9f240aa6011ba6f52dafbb" + integrity sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-unicode@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz#f67297fca3fea7f17e0d2caa40769afc487aa030" + integrity sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA== + dependencies: + browserslist "^4.21.4" + postcss-value-parser "^4.2.0" + +postcss-normalize-url@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz#ed9d88ca82e21abef99f743457d3729a042adcdc" + integrity sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew== + dependencies: + normalize-url "^6.0.1" + postcss-value-parser "^4.2.0" + +postcss-normalize-whitespace@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz#08a1a0d1ffa17a7cc6efe1e6c9da969cc4493cfa" + integrity sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-ordered-values@^5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz#b6fd2bd10f937b23d86bc829c69e7732ce76ea38" + integrity sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ== + dependencies: + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" + +postcss-reduce-idents@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-5.2.0.tgz#c89c11336c432ac4b28792f24778859a67dfba95" + integrity sha512-BTrLjICoSB6gxbc58D5mdBK8OhXRDqud/zodYfdSi52qvDHdMwk+9kB9xsM8yJThH/sZU5A6QVSmMmaN001gIg== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-reduce-initial@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz#798cd77b3e033eae7105c18c9d371d989e1382d6" + integrity sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg== + dependencies: + browserslist "^4.21.4" + caniuse-api "^3.0.0" + +postcss-reduce-transforms@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz#333b70e7758b802f3dd0ddfe98bb1ccfef96b6e9" + integrity sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9: + version "6.0.13" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz#d05d8d76b1e8e173257ef9d60b706a8e5e99bf1b" + integrity sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-sort-media-queries@^4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/postcss-sort-media-queries/-/postcss-sort-media-queries-4.4.1.tgz#04a5a78db3921eb78f28a1a781a2e68e65258128" + integrity sha512-QDESFzDDGKgpiIh4GYXsSy6sek2yAwQx1JASl5AxBtU1Lq2JfKBljIPNdil989NcSKRQX1ToiaKphImtBuhXWw== + dependencies: + sort-css-media-queries "2.1.0" + +postcss-svgo@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.1.0.tgz#0a317400ced789f233a28826e77523f15857d80d" + integrity sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA== + dependencies: + postcss-value-parser "^4.2.0" + svgo "^2.7.0" + +postcss-unique-selectors@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz#a9f273d1eacd09e9aa6088f4b0507b18b1b541b6" + integrity sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA== + dependencies: + postcss-selector-parser "^6.0.5" + +postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss-zindex@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-5.1.0.tgz#4a5c7e5ff1050bd4c01d95b1847dfdcc58a496ff" + integrity sha512-fgFMf0OtVSBR1va1JNHYgMxYk73yhn/qb4uQDq1DLGYolz8gHCyr/sesEuGUaYs58E3ZJRcpoGuPVoB7Meiq9A== + +postcss@^8.4.17, postcss@^8.4.21: + version "8.4.27" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.27.tgz#234d7e4b72e34ba5a92c29636734349e0d9c3057" + integrity sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ== + dependencies: + nanoid "^3.3.6" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +postcss@^8.4.26: + version "8.4.32" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.32.tgz#1dac6ac51ab19adb21b8b34fd2d93a86440ef6c9" + integrity sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +prebuild-install@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" + integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw== + dependencies: + detect-libc "^2.0.0" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" + napi-build-utils "^1.0.1" + node-abi "^3.3.0" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^4.0.0" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + +pretty-error@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-4.0.0.tgz#90a703f46dd7234adb46d0f84823e9d1cb8f10d6" + integrity sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw== + dependencies: + lodash "^4.17.20" + renderkid "^3.0.0" + +pretty-time@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pretty-time/-/pretty-time-1.1.0.tgz#ffb7429afabb8535c346a34e41873adf3d74dd0e" + integrity sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA== + +prism-react-renderer@^2.3.0, prism-react-renderer@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-2.3.1.tgz#e59e5450052ede17488f6bc85de1553f584ff8d5" + integrity sha512-Rdf+HzBLR7KYjzpJ1rSoxT9ioO85nZngQEoFIhL07XhtJHlCU3SOz0GJ6+qvMyQe0Se+BV3qpe6Yd/NmQF5Juw== + dependencies: + "@types/prismjs" "^1.26.0" + clsx "^2.0.0" + +prismjs@^1.29.0: + version "1.29.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.29.0.tgz#f113555a8fa9b57c35e637bba27509dcf802dd12" + integrity sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q== + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +prompts@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +prop-types@^15.0.0, prop-types@^15.6.2, prop-types@^15.7.2: + 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" + +property-information@^6.0.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.4.0.tgz#6bc4c618b0c2d68b3bb8b552cbb97f8e300a0f82" + integrity sha512-9t5qARVofg2xQqKtytzt+lZ4d1Qvj8t5B8fEwXK6qOfgRLgH/b13QlgEyDh033NOS31nXeFbYv7CLUDG1CeifQ== + +proto-list@~1.2.1: + version "1.2.4" + resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" + integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== + +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +punycode@^1.3.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== + +punycode@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" + integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== + +pupa@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/pupa/-/pupa-3.1.0.tgz#f15610274376bbcc70c9a3aa8b505ea23f41c579" + integrity sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug== + dependencies: + escape-goat "^4.0.0" + +qs@6.11.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" + +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== + +queue-tick@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/queue-tick/-/queue-tick-1.0.1.tgz#f6f07ac82c1fd60f82e098b417a80e52f1f4c142" + integrity sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag== + +queue@6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/queue/-/queue-6.0.2.tgz#b91525283e2315c7553d2efa18d83e76432fed65" + integrity sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA== + dependencies: + inherits "~2.0.3" + +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +range-parser@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" + integrity sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A== + +range-parser@^1.2.1, range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" + integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +rc@1.2.8, rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +react-dev-utils@^12.0.1: + version "12.0.1" + resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.1.tgz#ba92edb4a1f379bd46ccd6bcd4e7bc398df33e73" + integrity sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ== + dependencies: + "@babel/code-frame" "^7.16.0" + address "^1.1.2" + browserslist "^4.18.1" + chalk "^4.1.2" + cross-spawn "^7.0.3" + detect-port-alt "^1.1.6" + escape-string-regexp "^4.0.0" + filesize "^8.0.6" + find-up "^5.0.0" + fork-ts-checker-webpack-plugin "^6.5.0" + global-modules "^2.0.0" + globby "^11.0.4" + gzip-size "^6.0.0" + immer "^9.0.7" + is-root "^2.1.0" + loader-utils "^3.2.0" + open "^8.4.0" + pkg-up "^3.1.0" + prompts "^2.4.2" + react-error-overlay "^6.0.11" + recursive-readdir "^2.2.2" + shell-quote "^1.7.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +react-dom@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" + integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== + dependencies: + loose-envify "^1.1.0" + scheduler "^0.23.0" + +react-error-overlay@^6.0.11: + version "6.0.11" + resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb" + integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg== + +react-fast-compare@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" + integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== + +react-helmet-async@*, react-helmet-async@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/react-helmet-async/-/react-helmet-async-1.3.0.tgz#7bd5bf8c5c69ea9f02f6083f14ce33ef545c222e" + integrity sha512-9jZ57/dAn9t3q6hneQS0wukqC2ENOBgMNVEhb/ZG9ZSxUetzVIw4iAmEU38IaVg3QGYauQPhSeUTuIUtFglWpg== + dependencies: + "@babel/runtime" "^7.12.5" + invariant "^2.2.4" + prop-types "^15.7.2" + react-fast-compare "^3.2.0" + shallowequal "^1.1.0" + +react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0: + 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-is@^17.0.1 || ^18.0.0": + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== + +react-json-view-lite@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/react-json-view-lite/-/react-json-view-lite-1.2.1.tgz#c59a0bea4ede394db331d482ee02e293d38f8218" + integrity sha512-Itc0g86fytOmKZoIoJyGgvNqohWSbh3NXIKNgH6W6FT9PC1ck4xas1tT3Rr/b3UlFXyA9Jjaw9QSXdZy2JwGMQ== + +react-loadable-ssr-addon-v5-slorber@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz#2cdc91e8a744ffdf9e3556caabeb6e4278689883" + integrity sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A== + dependencies: + "@babel/runtime" "^7.10.3" + +react-router-config@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/react-router-config/-/react-router-config-5.1.1.tgz#0f4263d1a80c6b2dc7b9c1902c9526478194a988" + integrity sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg== + dependencies: + "@babel/runtime" "^7.1.2" + +react-router-dom@^5.3.4: + version "5.3.4" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.3.4.tgz#2ed62ffd88cae6db134445f4a0c0ae8b91d2e5e6" + integrity sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ== + dependencies: + "@babel/runtime" "^7.12.13" + history "^4.9.0" + loose-envify "^1.3.1" + prop-types "^15.6.2" + react-router "5.3.4" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + +react-router@5.3.4, react-router@^5.3.4: + version "5.3.4" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.3.4.tgz#8ca252d70fcc37841e31473c7a151cf777887bb5" + integrity sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA== + dependencies: + "@babel/runtime" "^7.12.13" + history "^4.9.0" + hoist-non-react-statics "^3.1.0" + loose-envify "^1.3.1" + path-to-regexp "^1.7.0" + prop-types "^15.6.2" + react-is "^16.6.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + +react-waypoint@^10.3.0: + version "10.3.0" + resolved "https://registry.yarnpkg.com/react-waypoint/-/react-waypoint-10.3.0.tgz#fcc60e86c6c9ad2174fa58d066dc6ae54e3df71d" + integrity sha512-iF1y2c1BsoXuEGz08NoahaLFIGI9gTUAAOKip96HUmylRT6DUtpgoBPjk/Y8dfcFVmfVDvUzWjNXpZyKTOV0SQ== + dependencies: + "@babel/runtime" "^7.12.5" + consolidated-events "^1.1.0 || ^2.0.0" + prop-types "^15.0.0" + react-is "^17.0.1 || ^18.0.0" + +react@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" + integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== + dependencies: + loose-envify "^1.1.0" + +readable-stream@^2.0.1: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + 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.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +reading-time@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/reading-time/-/reading-time-1.5.0.tgz#d2a7f1b6057cb2e169beaf87113cc3411b5bc5bb" + integrity sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg== + +rechoir@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw== + dependencies: + resolve "^1.1.6" + +recursive-readdir@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.3.tgz#e726f328c0d69153bcabd5c322d3195252379372" + integrity sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA== + dependencies: + minimatch "^3.0.5" + +regenerate-unicode-properties@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c" + integrity sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ== + dependencies: + regenerate "^1.4.2" + +regenerate@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" + integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== + +regenerator-runtime@^0.13.11: + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== + +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + +regenerator-transform@^0.15.1: + version "0.15.1" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.1.tgz#f6c4e99fc1b4591f780db2586328e4d9a9d8dc56" + integrity sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg== + dependencies: + "@babel/runtime" "^7.8.4" + +regenerator-transform@^0.15.2: + version "0.15.2" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.2.tgz#5bbae58b522098ebdf09bca2f83838929001c7a4" + integrity sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg== + dependencies: + "@babel/runtime" "^7.8.4" + +regexpu-core@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.2.tgz#11a2b06884f3527aec3e93dbbf4a3b958a95546b" + integrity sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ== + dependencies: + "@babel/regjsgen" "^0.8.0" + regenerate "^1.4.2" + regenerate-unicode-properties "^10.1.0" + regjsparser "^0.9.1" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.1.0" + +registry-auth-token@^5.0.1: + version "5.0.2" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-5.0.2.tgz#8b026cc507c8552ebbe06724136267e63302f756" + integrity sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ== + dependencies: + "@pnpm/npm-conf" "^2.1.0" + +registry-url@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-6.0.1.tgz#056d9343680f2f64400032b1e199faa692286c58" + integrity sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q== + dependencies: + rc "1.2.8" + +regjsparser@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709" + integrity sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ== + dependencies: + jsesc "~0.5.0" + +rehype-raw@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/rehype-raw/-/rehype-raw-7.0.0.tgz#59d7348fd5dbef3807bbaa1d443efd2dd85ecee4" + integrity sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww== + dependencies: + "@types/hast" "^3.0.0" + hast-util-raw "^9.0.0" + vfile "^6.0.0" + +relateurl@^0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" + integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog== + +remark-directive@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/remark-directive/-/remark-directive-3.0.0.tgz#34452d951b37e6207d2e2a4f830dc33442923268" + integrity sha512-l1UyWJ6Eg1VPU7Hm/9tt0zKtReJQNOA4+iDMAxTyZNWnJnFlbS/7zhiel/rogTLQ2vMYwDzSJa4BiVNqGlqIMA== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-directive "^3.0.0" + micromark-extension-directive "^3.0.0" + unified "^11.0.0" + +remark-emoji@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/remark-emoji/-/remark-emoji-4.0.1.tgz#671bfda668047689e26b2078c7356540da299f04" + integrity sha512-fHdvsTR1dHkWKev9eNyhTo4EFwbUvJ8ka9SgeWkMPYFX4WoI7ViVBms3PjlQYgw5TLvNQso3GUB/b/8t3yo+dg== + dependencies: + "@types/mdast" "^4.0.2" + emoticon "^4.0.1" + mdast-util-find-and-replace "^3.0.1" + node-emoji "^2.1.0" + unified "^11.0.4" + +remark-frontmatter@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/remark-frontmatter/-/remark-frontmatter-5.0.0.tgz#b68d61552a421ec412c76f4f66c344627dc187a2" + integrity sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-frontmatter "^2.0.0" + micromark-extension-frontmatter "^2.0.0" + unified "^11.0.0" + +remark-gfm@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/remark-gfm/-/remark-gfm-4.0.0.tgz#aea777f0744701aa288b67d28c43565c7e8c35de" + integrity sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-gfm "^3.0.0" + micromark-extension-gfm "^3.0.0" + remark-parse "^11.0.0" + remark-stringify "^11.0.0" + unified "^11.0.0" + +remark-mdx@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/remark-mdx/-/remark-mdx-3.0.0.tgz#146905a3925b078970e05fc89b0e16b9cc3bfddd" + integrity sha512-O7yfjuC6ra3NHPbRVxfflafAj3LTwx3b73aBvkEFU5z4PsD6FD4vrqJAkE5iNGLz71GdjXfgRqm3SQ0h0VuE7g== + dependencies: + mdast-util-mdx "^3.0.0" + micromark-extension-mdxjs "^3.0.0" + +remark-parse@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-11.0.0.tgz#aa60743fcb37ebf6b069204eb4da304e40db45a1" + integrity sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-from-markdown "^2.0.0" + micromark-util-types "^2.0.0" + unified "^11.0.0" + +remark-rehype@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-11.0.0.tgz#7f21c08738bde024be5f16e4a8b13e5d7a04cf6b" + integrity sha512-vx8x2MDMcxuE4lBmQ46zYUDfcFMmvg80WYX+UNLeG6ixjdCCLcw1lrgAukwBTuOFsS78eoAedHGn9sNM0w7TPw== + dependencies: + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + mdast-util-to-hast "^13.0.0" + unified "^11.0.0" + vfile "^6.0.0" + +remark-stringify@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-11.0.0.tgz#4c5b01dd711c269df1aaae11743eb7e2e7636fd3" + integrity sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-to-markdown "^2.0.0" + unified "^11.0.0" + +renderkid@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-3.0.0.tgz#5fd823e4d6951d37358ecc9a58b1f06836b6268a" + integrity sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg== + dependencies: + css-select "^4.1.3" + dom-converter "^0.2.0" + htmlparser2 "^6.1.0" + lodash "^4.17.21" + strip-ansi "^6.0.1" + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +"require-like@>= 0.1.1": + version "0.1.2" + resolved "https://registry.yarnpkg.com/require-like/-/require-like-0.1.2.tgz#ad6f30c13becd797010c468afa775c0c0a6b47fa" + integrity sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A== + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + +resolve-alpn@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" + integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== + +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-pathname@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" + integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== + +resolve@^1.1.6, resolve@^1.14.2: + version "1.22.2" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f" + integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g== + dependencies: + is-core-module "^2.11.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +responselike@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-3.0.0.tgz#20decb6c298aff0dbee1c355ca95461d42823626" + integrity sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg== + dependencies: + lowercase-keys "^3.0.0" + +retry@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" + integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== + +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" + +rtl-detect@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/rtl-detect/-/rtl-detect-1.0.4.tgz#40ae0ea7302a150b96bc75af7d749607392ecac6" + integrity sha512-EBR4I2VDSSYr7PkBmFy04uhycIpDKp+21p/jARYXlCSjQksTBQcJ0HFUPOO79EPPH5JS6VAhiIQbycf0O3JAxQ== + +rtlcss@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/rtlcss/-/rtlcss-4.1.1.tgz#f20409fcc197e47d1925996372be196fee900c0c" + integrity sha512-/oVHgBtnPNcggP2aVXQjSy6N1mMAfHg4GSag0QtZBlD5bdDgAHwr4pydqJGd+SUCu9260+Pjqbjwtvu7EMH1KQ== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + postcss "^8.4.21" + strip-json-comments "^3.1.1" + +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.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, 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== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sax@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +scheduler@^0.23.0: + version "0.23.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" + integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== + dependencies: + loose-envify "^1.1.0" + +schema-utils@2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" + integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== + dependencies: + "@types/json-schema" "^7.0.4" + ajv "^6.12.2" + ajv-keywords "^3.4.1" + +schema-utils@^3.0.0, schema-utils@^3.1.1, schema-utils@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" + integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== + dependencies: + "@types/json-schema" "^7.0.8" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + +schema-utils@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.2.0.tgz#70d7c93e153a273a805801882ebd3bff20d89c8b" + integrity sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw== + dependencies: + "@types/json-schema" "^7.0.9" + ajv "^8.9.0" + ajv-formats "^2.1.1" + ajv-keywords "^5.1.0" + +section-matter@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/section-matter/-/section-matter-1.0.0.tgz#e9041953506780ec01d59f292a19c7b850b84167" + integrity sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA== + dependencies: + extend-shallow "^2.0.1" + kind-of "^6.0.0" + +select-hose@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" + integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== + +selfsigned@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.1.1.tgz#18a7613d714c0cd3385c48af0075abf3f266af61" + integrity sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ== + dependencies: + node-forge "^1" + +semver-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-4.0.0.tgz#3afcf5ed6d62259f5c72d0d5d50dffbdc9680df5" + integrity sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA== + dependencies: + semver "^7.3.5" + +semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.4: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + dependencies: + lru-cache "^6.0.0" + +send@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +serialize-javascript@^6.0.0, serialize-javascript@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c" + integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w== + dependencies: + randombytes "^2.1.0" + +serve-handler@^6.1.5: + version "6.1.5" + resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.5.tgz#a4a0964f5c55c7e37a02a633232b6f0d6f068375" + integrity sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg== + dependencies: + bytes "3.0.0" + content-disposition "0.5.2" + fast-url-parser "1.1.3" + mime-types "2.1.18" + minimatch "3.1.2" + path-is-inside "1.0.2" + path-to-regexp "2.2.1" + range-parser "1.2.0" + +serve-index@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" + integrity sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw== + dependencies: + accepts "~1.3.4" + batch "0.6.1" + debug "2.6.9" + escape-html "~1.0.3" + http-errors "~1.6.2" + mime-types "~2.1.17" + parseurl "~1.3.2" + +serve-static@1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.18.0" + +setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + +shallowequal@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" + integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== + +sharp@^0.32.3: + version "0.32.6" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.32.6.tgz#6ad30c0b7cd910df65d5f355f774aa4fce45732a" + integrity sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w== + dependencies: + color "^4.2.3" + detect-libc "^2.0.2" + node-addon-api "^6.1.0" + prebuild-install "^7.1.1" + semver "^7.5.4" + simple-get "^4.0.1" + tar-fs "^3.0.4" + tunnel-agent "^0.6.0" + +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== + +shell-quote@^1.7.3: + version "1.8.1" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680" + integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== + +shelljs@^0.8.5: + version "0.8.5" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" + integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + +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.2, signal-exit@^3.0.3: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^4.0.0, simple-get@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" + integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== + dependencies: + decompress-response "^6.0.0" + once "^1.3.1" + simple-concat "^1.0.0" + +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== + dependencies: + is-arrayish "^0.3.1" + +sirv@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.4.tgz#5dd9a725c578e34e449f332703eb2a74e46a29b0" + integrity sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ== + dependencies: + "@polka/url" "^1.0.0-next.24" + mrmime "^2.0.0" + totalist "^3.0.0" + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +sitemap@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/sitemap/-/sitemap-7.1.1.tgz#eeed9ad6d95499161a3eadc60f8c6dce4bea2bef" + integrity sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg== + dependencies: + "@types/node" "^17.0.5" + "@types/sax" "^1.2.1" + arg "^5.0.0" + sax "^1.2.4" + +skin-tone@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/skin-tone/-/skin-tone-2.0.0.tgz#4e3933ab45c0d4f4f781745d64b9f4c208e41237" + integrity sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA== + dependencies: + unicode-emoji-modifier-base "^1.0.0" + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +slash@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" + integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== + +sockjs@^0.3.24: + version "0.3.24" + resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" + integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== + dependencies: + faye-websocket "^0.11.3" + uuid "^8.3.2" + websocket-driver "^0.7.4" + +sort-css-media-queries@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/sort-css-media-queries/-/sort-css-media-queries-2.1.0.tgz#7c85e06f79826baabb232f5560e9745d7a78c4ce" + integrity sha512-IeWvo8NkNiY2vVYdPa27MCQiR0MN0M80johAYFVxWWXQ44KU84WNxjslwBHmc/7ZL2ccwkM7/e6S5aiKZXm7jA== + +source-map-js@^1.0.2: + 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-support@~0.5.20: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.7.0: + version "0.7.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" + integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== + +space-separated-tokens@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz#1ecd9d2350a3844572c3f4a312bceb018348859f" + integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q== + +spdy-transport@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" + integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== + dependencies: + debug "^4.1.0" + detect-node "^2.0.4" + hpack.js "^2.1.6" + obuf "^1.1.2" + readable-stream "^3.0.6" + wbuf "^1.7.3" + +spdy@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" + integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== + dependencies: + debug "^4.1.0" + handle-thing "^2.0.0" + http-deceiver "^1.2.7" + select-hose "^2.0.0" + spdy-transport "^3.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +srcset@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/srcset/-/srcset-4.0.0.tgz#336816b665b14cd013ba545b6fe62357f86e65f4" + integrity sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw== + +stable@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" + integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== + +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +"statuses@>= 1.4.0 < 2": + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== + +std-env@^3.0.1: + version "3.3.3" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.3.3.tgz#a54f06eb245fdcfef53d56f3c0251f1d5c3d01fe" + integrity sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg== + +streamx@^2.15.0: + version "2.15.6" + resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.15.6.tgz#28bf36997ebc7bf6c08f9eba958735231b833887" + integrity sha512-q+vQL4AAz+FdfT137VF69Cc/APqUbxy+MDOImRrMvchJpigHj9GksgDU2LYbO9rx7RX6osWgxJB2WxhYv4SZAw== + dependencies: + fast-fifo "^1.1.0" + queue-tick "^1.0.1" + +string-width@^4.1.0, string-width@^4.2.0: + 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-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +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" + +stringify-entities@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-4.0.3.tgz#cfabd7039d22ad30f3cc435b0ca2c1574fc88ef8" + integrity sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g== + dependencies: + character-entities-html4 "^2.0.0" + character-entities-legacy "^3.0.0" + +stringify-object@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" + integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw== + dependencies: + get-own-enumerable-property-symbols "^3.0.0" + is-obj "^1.0.1" + is-regexp "^1.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-ansi@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + +strip-bom-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92" + integrity sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + +style-to-object@^0.4.0: + version "0.4.4" + resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.4.4.tgz#266e3dfd56391a7eefb7770423612d043c3f33ec" + integrity sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg== + dependencies: + inline-style-parser "0.1.1" + +style-to-object@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-1.0.5.tgz#5e918349bc3a39eee3a804497d97fcbbf2f0d7c0" + integrity sha512-rDRwHtoDD3UMMrmZ6BzOW0naTjMsVZLIjsGleSKS/0Oz+cgCfAPRspaqJuE8rDzpKha/nEvnM0IF4seEAZUTKQ== + dependencies: + inline-style-parser "0.2.2" + +stylehacks@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.1.1.tgz#7934a34eb59d7152149fa69d6e9e56f2fc34bcc9" + integrity sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw== + dependencies: + browserslist "^4.21.4" + postcss-selector-parser "^6.0.4" + +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-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-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== + +svg-parser@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" + integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== + +svgo@^2.7.0, svgo@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" + integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== + dependencies: + "@trysound/sax" "0.2.0" + commander "^7.2.0" + css-select "^4.1.3" + css-tree "^1.1.3" + csso "^4.2.0" + picocolors "^1.0.0" + stable "^0.1.8" + +tapable@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" + integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== + +tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + +tar-fs@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" + integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" + +tar-fs@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-3.0.4.tgz#a21dc60a2d5d9f55e0089ccd78124f1d3771dbbf" + integrity sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w== + dependencies: + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^3.1.5" + +tar-stream@^2.1.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + +tar-stream@^3.1.5: + version "3.1.6" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-3.1.6.tgz#6520607b55a06f4a2e2e04db360ba7d338cc5bab" + integrity sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg== + dependencies: + b4a "^1.6.4" + fast-fifo "^1.2.0" + streamx "^2.15.0" + +terser-webpack-plugin@^5.3.7, terser-webpack-plugin@^5.3.9: + version "5.3.9" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz#832536999c51b46d468067f9e37662a3b96adfe1" + integrity sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.17" + jest-worker "^27.4.5" + schema-utils "^3.1.1" + serialize-javascript "^6.0.1" + terser "^5.16.8" + +terser@^5.10.0, terser@^5.16.8: + version "5.19.2" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.19.2.tgz#bdb8017a9a4a8de4663a7983f45c506534f9234e" + integrity sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA== + dependencies: + "@jridgewell/source-map" "^0.3.3" + acorn "^8.8.2" + commander "^2.20.0" + source-map-support "~0.5.20" + +terser@^5.15.1: + version "5.26.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.26.0.tgz#ee9f05d929f4189a9c28a0feb889d96d50126fe1" + integrity sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ== + dependencies: + "@jridgewell/source-map" "^0.3.3" + acorn "^8.8.2" + commander "^2.20.0" + source-map-support "~0.5.20" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +thunky@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" + integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== + +tiny-invariant@^1.0.2: + version "1.3.1" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642" + integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw== + +tiny-warning@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +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" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +totalist@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.1.tgz#ba3a3d600c915b1a97872348f79c127475f6acf8" + integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ== + +trim-lines@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338" + integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg== + +trough@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/trough/-/trough-2.1.0.tgz#0f7b511a4fde65a46f18477ab38849b22c554876" + integrity sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g== + +tslib@^2.0.3: + version "2.6.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.0.tgz#b295854684dbda164e181d259a22cd779dcd7bc3" + integrity sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA== + +tslib@^2.6.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== + dependencies: + safe-buffer "^5.0.1" + +type-fest@^1.0.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1" + integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== + +type-fest@^2.13.0, type-fest@^2.5.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" + integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + +typescript@^5.3.3: + version "5.3.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" + integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== + +unicode-canonical-property-names-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" + integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== + +unicode-emoji-modifier-base@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz#dbbd5b54ba30f287e2a8d5a249da6c0cef369459" + integrity sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g== + +unicode-match-property-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" + integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== + dependencies: + unicode-canonical-property-names-ecmascript "^2.0.0" + unicode-property-aliases-ecmascript "^2.0.0" + +unicode-match-property-value-ecmascript@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz#cb5fffdcd16a05124f5a4b0bf7c3770208acbbe0" + integrity sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA== + +unicode-property-aliases-ecmascript@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" + integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== + +unified@^11.0.0, unified@^11.0.3, unified@^11.0.4: + version "11.0.4" + resolved "https://registry.yarnpkg.com/unified/-/unified-11.0.4.tgz#f4be0ac0fe4c88cb873687c07c64c49ed5969015" + integrity sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ== + dependencies: + "@types/unist" "^3.0.0" + bail "^2.0.0" + devlop "^1.0.0" + extend "^3.0.0" + is-plain-obj "^4.0.0" + trough "^2.0.0" + vfile "^6.0.0" + +unique-string@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-3.0.0.tgz#84a1c377aff5fd7a8bc6b55d8244b2bd90d75b9a" + integrity sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ== + dependencies: + crypto-random-string "^4.0.0" + +unist-util-is@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-6.0.0.tgz#b775956486aff107a9ded971d996c173374be424" + integrity sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-position-from-estree@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz#d94da4df596529d1faa3de506202f0c9a23f2200" + integrity sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-position@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-5.0.0.tgz#678f20ab5ca1207a97d7ea8a388373c9cf896be4" + integrity sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-remove-position@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz#fea68a25658409c9460408bc6b4991b965b52163" + integrity sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q== + dependencies: + "@types/unist" "^3.0.0" + unist-util-visit "^5.0.0" + +unist-util-stringify-position@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz#449c6e21a880e0855bf5aabadeb3a740314abac2" + integrity sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-visit-parents@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz#4d5f85755c3b8f0dc69e21eca5d6d82d22162815" + integrity sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw== + dependencies: + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + +unist-util-visit@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.0.0.tgz#a7de1f31f72ffd3519ea71814cccf5fd6a9217d6" + integrity sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg== + dependencies: + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + unist-util-visit-parents "^6.0.0" + +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +update-browserslist-db@^1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940" + integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +update-browserslist-db@^1.0.13: + version "1.0.13" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" + integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +update-notifier@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-6.0.2.tgz#a6990253dfe6d5a02bd04fbb6a61543f55026b60" + integrity sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og== + dependencies: + boxen "^7.0.0" + chalk "^5.0.1" + configstore "^6.0.0" + has-yarn "^3.0.0" + import-lazy "^4.0.0" + is-ci "^3.0.1" + is-installed-globally "^0.4.0" + is-npm "^6.0.0" + is-yarn-global "^0.4.0" + latest-version "^7.0.0" + pupa "^3.1.0" + semver "^7.3.7" + semver-diff "^4.0.0" + xdg-basedir "^5.1.0" + +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" + +url-loader@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-4.1.1.tgz#28505e905cae158cf07c92ca622d7f237e70a4e2" + integrity sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA== + dependencies: + loader-utils "^2.0.0" + mime-types "^2.1.27" + schema-utils "^3.0.0" + +util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +utila@~0.4: + version "0.4.0" + resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" + integrity sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA== + +utility-types@^3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.10.0.tgz#ea4148f9a741015f05ed74fd615e1d20e6bed82b" + integrity sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg== + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +value-equal@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" + integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw== + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +vfile-location@^5.0.0: + version "5.0.2" + resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-5.0.2.tgz#220d9ca1ab6f8b2504a4db398f7ebc149f9cb464" + integrity sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg== + dependencies: + "@types/unist" "^3.0.0" + vfile "^6.0.0" + +vfile-message@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-4.0.2.tgz#c883c9f677c72c166362fd635f21fc165a7d1181" + integrity sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw== + dependencies: + "@types/unist" "^3.0.0" + unist-util-stringify-position "^4.0.0" + +vfile@^6.0.0, vfile@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-6.0.1.tgz#1e8327f41eac91947d4fe9d237a2dd9209762536" + integrity sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw== + dependencies: + "@types/unist" "^3.0.0" + unist-util-stringify-position "^4.0.0" + vfile-message "^4.0.0" + +watchpack@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" + integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + +wbuf@^1.1.0, wbuf@^1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" + integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== + dependencies: + minimalistic-assert "^1.0.0" + +web-namespaces@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-2.0.1.tgz#1010ff7c650eccb2592cebeeaf9a1b253fd40692" + integrity sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ== + +webpack-bundle-analyzer@^4.9.0: + version "4.10.1" + resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.1.tgz#84b7473b630a7b8c21c741f81d8fe4593208b454" + integrity sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ== + dependencies: + "@discoveryjs/json-ext" "0.5.7" + acorn "^8.0.4" + acorn-walk "^8.0.0" + commander "^7.2.0" + debounce "^1.2.1" + escape-string-regexp "^4.0.0" + gzip-size "^6.0.0" + html-escaper "^2.0.2" + is-plain-object "^5.0.0" + opener "^1.5.2" + picocolors "^1.0.0" + sirv "^2.0.3" + ws "^7.3.1" + +webpack-dev-middleware@^5.3.1: + version "5.3.3" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz#efae67c2793908e7311f1d9b06f2a08dcc97e51f" + integrity sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA== + dependencies: + colorette "^2.0.10" + memfs "^3.4.3" + mime-types "^2.1.31" + range-parser "^1.2.1" + schema-utils "^4.0.0" + +webpack-dev-server@^4.15.1: + version "4.15.1" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz#8944b29c12760b3a45bdaa70799b17cb91b03df7" + integrity sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA== + dependencies: + "@types/bonjour" "^3.5.9" + "@types/connect-history-api-fallback" "^1.3.5" + "@types/express" "^4.17.13" + "@types/serve-index" "^1.9.1" + "@types/serve-static" "^1.13.10" + "@types/sockjs" "^0.3.33" + "@types/ws" "^8.5.5" + ansi-html-community "^0.0.8" + bonjour-service "^1.0.11" + chokidar "^3.5.3" + colorette "^2.0.10" + compression "^1.7.4" + connect-history-api-fallback "^2.0.0" + default-gateway "^6.0.3" + express "^4.17.3" + graceful-fs "^4.2.6" + html-entities "^2.3.2" + http-proxy-middleware "^2.0.3" + ipaddr.js "^2.0.1" + launch-editor "^2.6.0" + open "^8.0.9" + p-retry "^4.5.0" + rimraf "^3.0.2" + schema-utils "^4.0.0" + selfsigned "^2.1.1" + serve-index "^1.9.1" + sockjs "^0.3.24" + spdy "^4.0.2" + webpack-dev-middleware "^5.3.1" + ws "^8.13.0" + +webpack-merge@^5.9.0: + version "5.10.0" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.10.0.tgz#a3ad5d773241e9c682803abf628d4cd62b8a4177" + integrity sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA== + dependencies: + clone-deep "^4.0.1" + flat "^5.0.2" + wildcard "^2.0.0" + +webpack-sources@^3.2.2, webpack-sources@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== + +webpack@^5.88.1: + version "5.89.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.89.0.tgz#56b8bf9a34356e93a6625770006490bf3a7f32dc" + integrity sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw== + dependencies: + "@types/eslint-scope" "^3.7.3" + "@types/estree" "^1.0.0" + "@webassemblyjs/ast" "^1.11.5" + "@webassemblyjs/wasm-edit" "^1.11.5" + "@webassemblyjs/wasm-parser" "^1.11.5" + acorn "^8.7.1" + acorn-import-assertions "^1.9.0" + browserslist "^4.14.5" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.15.0" + es-module-lexer "^1.2.1" + eslint-scope "5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.9" + json-parse-even-better-errors "^2.3.1" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^3.2.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.3.7" + watchpack "^2.4.0" + webpack-sources "^3.2.3" + +webpackbar@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/webpackbar/-/webpackbar-5.0.2.tgz#d3dd466211c73852741dfc842b7556dcbc2b0570" + integrity sha512-BmFJo7veBDgQzfWXl/wwYXr/VFus0614qZ8i9znqcl9fnEdiVkdbi0TedLQ6xAK92HZHDJ0QmyQ0fmuZPAgCYQ== + dependencies: + chalk "^4.1.0" + consola "^2.15.3" + pretty-time "^1.1.0" + std-env "^3.0.1" + +websocket-driver@>=0.5.1, websocket-driver@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" + integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== + dependencies: + http-parser-js ">=0.5.1" + safe-buffer ">=5.1.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.4" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" + integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== + +which@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +which@^2.0.1: + 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" + +widest-line@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-4.0.1.tgz#a0fc673aaba1ea6f0a0d35b3c2795c9a9cc2ebf2" + integrity sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig== + dependencies: + string-width "^5.0.1" + +wildcard@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67" + integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== + +wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +ws@^7.3.1: + version "7.5.9" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + +ws@^8.13.0: + version "8.13.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" + integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== + +xdg-basedir@^5.0.1, xdg-basedir@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-5.1.0.tgz#1efba19425e73be1bc6f2a6ceb52a3d2c884c0c9" + integrity sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ== + +xml-js@^1.6.11: + version "1.6.11" + resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9" + integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g== + dependencies: + sax "^1.2.4" + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +yocto-queue@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251" + integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== + +zwitch@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7" + integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A== diff --git a/apps/download-zip-sw/.babelrc b/apps/download-zip-sw/.babelrc index fb3fd65a5..f2f380674 100644 --- a/apps/download-zip-sw/.babelrc +++ b/apps/download-zip-sw/.babelrc @@ -1,5 +1,3 @@ { - "presets": [ - "@nrwl/js/babel" - ] + "presets": ["@nx/js/babel"] } diff --git a/apps/download-zip-sw/project.json b/apps/download-zip-sw/project.json index a571e5597..a30d0c50d 100644 --- a/apps/download-zip-sw/project.json +++ b/apps/download-zip-sw/project.json @@ -5,7 +5,7 @@ "sourceRoot": "apps/download-zip-sw/src", "targets": { "build": { - "executor": "@nrwl/webpack:webpack", + "executor": "@nx/webpack:webpack", "outputs": ["{options.outputPath}"], "options": { "outputPath": "dist/apps/download-zip-sw", @@ -48,7 +48,7 @@ "defaultConfiguration": "production" }, "serve": { - "executor": "@nrwl/webpack:dev-server", + "executor": "@nx/webpack:dev-server", "options": { "buildTarget": "download-zip-sw:build" }, @@ -63,18 +63,14 @@ "defaultConfiguration": "development" }, "lint": { - "executor": "@nrwl/linter:eslint", - "outputs": ["{options.outputFile}"], - "options": { - "lintFilePatterns": ["apps/download-zip-sw/**/*.ts"] - } + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"] }, "test": { - "executor": "@nrwl/jest:jest", + "executor": "@nx/jest:jest", "outputs": ["{workspaceRoot}/coverage/apps/download-zip-sw"], "options": { - "jestConfig": "apps/download-zip-sw/jest.config.ts", - "passWithNoTests": true + "jestConfig": "apps/download-zip-sw/jest.config.ts" } } }, diff --git a/apps/electron/jetstream/project.json b/apps/electron/jetstream/project.json index 1130cbed8..3bc3cc0d3 100644 --- a/apps/electron/jetstream/project.json +++ b/apps/electron/jetstream/project.json @@ -56,18 +56,14 @@ } }, "lint": { - "executor": "@nrwl/linter:eslint", - "outputs": ["{options.outputFile}"], - "options": { - "lintFilePatterns": ["apps/electron/jetstream/**/*.ts"] - } + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"] }, "test": { - "executor": "@nrwl/jest:jest", + "executor": "@nx/jest:jest", "outputs": ["{workspaceRoot}/coverage/apps/electron/jetstream"], "options": { - "jestConfig": "apps/electron/jetstream/jest.config.ts", - "passWithNoTests": true + "jestConfig": "apps/electron/jetstream/jest.config.ts" } } } diff --git a/apps/electron/jetstream/webpack.config.js b/apps/electron/jetstream/webpack.config.js index 6f9ce722f..c2f67039e 100644 --- a/apps/electron/jetstream/webpack.config.js +++ b/apps/electron/jetstream/webpack.config.js @@ -1,5 +1,5 @@ const { DefinePlugin } = require('webpack'); -const getWebpackConfig = require('@nrwl/react/plugins/webpack'); +const getWebpackConfig = require('@nx/react/plugins/webpack'); function getClientEnvironment() { // Grab NODE_ENV and NX_* environment variables and prepare them to be diff --git a/apps/electron/preferences/.babelrc b/apps/electron/preferences/.babelrc index c00edfd4b..1bfb12338 100644 --- a/apps/electron/preferences/.babelrc +++ b/apps/electron/preferences/.babelrc @@ -1,4 +1,4 @@ { - "presets": [["@nrwl/react/babel", { "runtime": "automatic", "importSource": "@emotion/react" }]], + "presets": [["@nx/react/babel", { "runtime": "automatic", "importSource": "@emotion/react" }]], "plugins": ["@emotion/babel-plugin"] } diff --git a/apps/electron/preferences/.eslintrc.json b/apps/electron/preferences/.eslintrc.json index 50e59482c..75b85077d 100644 --- a/apps/electron/preferences/.eslintrc.json +++ b/apps/electron/preferences/.eslintrc.json @@ -1,5 +1,5 @@ { - "extends": ["plugin:@nrwl/nx/react", "../../../.eslintrc.json"], + "extends": ["plugin:@nx/react", "../../../.eslintrc.json"], "ignorePatterns": ["!**/*"], "overrides": [ { diff --git a/apps/electron/preferences/project.json b/apps/electron/preferences/project.json index fef02d58f..a4bf52bb5 100644 --- a/apps/electron/preferences/project.json +++ b/apps/electron/preferences/project.json @@ -5,7 +5,7 @@ "projectType": "application", "targets": { "build": { - "executor": "@nrwl/webpack:webpack", + "executor": "@nx/webpack:webpack", "outputs": ["{options.outputPath}"], "defaultConfiguration": "production", "options": { @@ -48,7 +48,7 @@ } }, "serve": { - "executor": "@nrwl/webpack:dev-server", + "executor": "@nx/webpack:dev-server", "defaultConfiguration": "development", "options": { "buildTarget": "electron-preferences:build", @@ -65,11 +65,8 @@ } }, "lint": { - "executor": "@nrwl/linter:eslint", - "outputs": ["{options.outputFile}"], - "options": { - "lintFilePatterns": ["apps/electron/preferences/**/*.{ts,tsx,js,jsx}"] - } + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"] } }, "tags": ["electron"] diff --git a/apps/electron/preferences/src/styles.scss b/apps/electron/preferences/src/styles.scss index eda06dc7f..9fb6469ee 100644 --- a/apps/electron/preferences/src/styles.scss +++ b/apps/electron/preferences/src/styles.scss @@ -158,8 +158,11 @@ pre { &:not(.read-only) { cursor: pointer; } + &.disabled { + cursor: not-allowed; + } margin: 1px; - &:not(.read-only):hover { + &:not(.disabled):not(.read-only):hover { background-color: $color-background; } &:not(.read-only).is-active { diff --git a/apps/electron/preferences/tsconfig.app.json b/apps/electron/preferences/tsconfig.app.json index ca878c1b3..66cdaf7c8 100644 --- a/apps/electron/preferences/tsconfig.app.json +++ b/apps/electron/preferences/tsconfig.app.json @@ -5,7 +5,7 @@ "strictNullChecks": true, "types": ["node", "google.accounts", "google.picker", "gapi.auth2", "gapi.client.drive"] }, - "files": ["../../../node_modules/@nrwl/react/typings/cssmodule.d.ts", "../../../node_modules/@nrwl/react/typings/image.d.ts"], + "files": ["../../../node_modules/@nx/react/typings/cssmodule.d.ts", "../../../node_modules/@nx/react/typings/image.d.ts"], "exclude": [ "jest.config.ts", "**/*.spec.ts", diff --git a/apps/electron/preferences/webpack.config.js b/apps/electron/preferences/webpack.config.js index 5515c1723..e0d09b386 100644 --- a/apps/electron/preferences/webpack.config.js +++ b/apps/electron/preferences/webpack.config.js @@ -1,5 +1,5 @@ -const { composePlugins, withNx } = require('@nrwl/webpack'); -const { withReact } = require('@nrwl/react'); +const { composePlugins, withNx } = require('@nx/webpack'); +const { withReact } = require('@nx/react'); // Nx plugins for webpack. module.exports = composePlugins(withNx(), withReact(), (config, { options, context }) => { diff --git a/apps/electron/worker/project.json b/apps/electron/worker/project.json index 5cf66364d..6079d508f 100644 --- a/apps/electron/worker/project.json +++ b/apps/electron/worker/project.json @@ -5,7 +5,7 @@ "projectType": "application", "targets": { "build": { - "executor": "@nrwl/webpack:webpack", + "executor": "@nx/webpack:webpack", "options": { "outputPath": "dist/apps/electron/worker", "main": "apps/electron/worker/src/main.ts", @@ -42,7 +42,7 @@ "defaultConfiguration": "production" }, "serve": { - "executor": "@nrwl/node:node", + "executor": "@nx/js:node", "options": { "buildTarget": "electron-worker:build", "inspect": true, @@ -50,18 +50,14 @@ } }, "lint": { - "executor": "@nrwl/linter:eslint", - "outputs": ["{options.outputFile}"], - "options": { - "lintFilePatterns": ["apps/electron/worker/**/*.ts"] - } + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"] }, "test": { - "executor": "@nrwl/jest:jest", + "executor": "@nx/jest:jest", "outputs": ["{workspaceRoot}/coverage/apps/electron/worker"], "options": { - "jestConfig": "apps/electron/worker/jest.config.ts", - "passWithNoTests": true + "jestConfig": "apps/electron/worker/jest.config.ts" } } }, diff --git a/apps/jetstream-e2e/project.json b/apps/jetstream-e2e/project.json index b6d283393..773cd5d10 100644 --- a/apps/jetstream-e2e/project.json +++ b/apps/jetstream-e2e/project.json @@ -19,11 +19,17 @@ }, "e2e-ci": { "executor": "nx:run-commands", - "outputs": ["apps/jetstream-e2e/playwright-report"], + "outputs": ["{projectRoot}/playwright-report"], "options": { "command": "yarn start-server-and-test --expect 200 'yarn start:e2e' http://localhost:3333 'yarn playwright:test'" }, - "dependsOn": [{ "projects": "dependencies", "target": "build", "params": "ignore" }] + "dependsOn": [ + { + "target": "build", + "params": "ignore", + "dependencies": true + } + ] }, "ts-check": { "executor": "nx:run-commands", @@ -36,11 +42,8 @@ } }, "lint": { - "executor": "@nrwl/linter:eslint", - "outputs": ["{options.outputFile}"], - "options": { - "lintFilePatterns": ["apps/jetstream-e2e/**/*.{ts,tsx,js,jsx}"] - } + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"] } }, "tags": ["e2e"], diff --git a/apps/jetstream-e2e/src/pageObjectModels/LoadSingleObjectPage.model.ts b/apps/jetstream-e2e/src/pageObjectModels/LoadSingleObjectPage.model.ts index dc5484773..de27ff610 100644 --- a/apps/jetstream-e2e/src/pageObjectModels/LoadSingleObjectPage.model.ts +++ b/apps/jetstream-e2e/src/pageObjectModels/LoadSingleObjectPage.model.ts @@ -97,6 +97,7 @@ export class LoadSingleObjectPage { // choose non-ext id // polymorphic fields // getByRole('button', { name: 'Continue to Load Records' }) + // Manually added fields } async loadRecords(apiType: 'Bulk API' | 'Batch API', isSubsequentLoad = false) { diff --git a/apps/jetstream-e2e/src/pageObjectModels/QueryPage.model.ts b/apps/jetstream-e2e/src/pageObjectModels/QueryPage.model.ts index 9756717ac..c23b06fae 100644 --- a/apps/jetstream-e2e/src/pageObjectModels/QueryPage.model.ts +++ b/apps/jetstream-e2e/src/pageObjectModels/QueryPage.model.ts @@ -1,10 +1,10 @@ -import { APIRequestContext, expect, Locator, Page } from '@playwright/test'; +import { QueryResults } from '@jetstream/api-interfaces'; +import { formatNumber } from '@jetstream/shared/ui-utils'; +import { isRecordWithId } from '@jetstream/shared/utils'; import { QueryFilterOperator } from '@jetstream/types'; +import { APIRequestContext, expect, Locator, Page } from '@playwright/test'; import isNumber from 'lodash/isNumber'; -import { formatNumber } from '@jetstream/shared/ui-utils'; -import { QueryResults } from '@jetstream/api-interfaces'; import { ApiRequestUtils } from '../fixtures/ApiRequestUtils'; -import { isRecordWithId } from '@jetstream/shared/utils'; export class QueryPage { readonly apiRequestUtils: ApiRequestUtils; @@ -200,10 +200,13 @@ export class QueryPage { // verify correct query shows up await this.page.getByRole('button', { name: 'SOQL Query' }).click(); - await expect(this.page.getByRole('code').locator('div').filter({ hasText: query }).first()).toBeVisible(); + // The full query is broken up in a weird way, we we check each token individually + for (const token of query.split(' ')) { + await expect(this.page.getByRole('code').locator('div').filter({ hasText: token }).first()).toBeVisible(); + } await this.page.getByRole('button', { name: 'Collapse SOQL Query' }).click(); - await this.page.getByRole('button', { name: 'History' }).click(); + await this.page.getByLabel('Query History').click(); // verify query history await expect( @@ -226,7 +229,7 @@ export class QueryPage { buttonName = 'Saved'; role = 'button'; } - await this.page.getByRole('button', { name: 'History' }).click(); + await this.page.getByLabel('Query History').click(); await this.page.getByTestId(`query-history-${query}`).getByRole(role, { name: buttonName }).click(); } diff --git a/apps/jetstream-e2e/src/tests/app/routing.spec.ts b/apps/jetstream-e2e/src/tests/app/routing.spec.ts index 3b16e91b4..74104de0a 100644 --- a/apps/jetstream-e2e/src/tests/app/routing.spec.ts +++ b/apps/jetstream-e2e/src/tests/app/routing.spec.ts @@ -24,7 +24,7 @@ const testCases = [ menu: 'Deploy Metadata', items: [ { link: 'Deploy and Compare Metadata', path: '/deploy-metadata' }, - { link: 'Create Fields', path: '/create-fields' }, + { link: 'Create Object and Fields', path: '/create-fields' }, { link: 'Formula Evaluator', path: '/formula-evaluator' }, ], }, diff --git a/apps/jetstream-worker/project.json b/apps/jetstream-worker/project.json index 492f1d576..3cc1c9d75 100644 --- a/apps/jetstream-worker/project.json +++ b/apps/jetstream-worker/project.json @@ -5,7 +5,7 @@ "projectType": "application", "targets": { "build": { - "executor": "@nrwl/webpack:webpack", + "executor": "@nx/webpack:webpack", "outputs": ["{options.outputPath}"], "options": { "outputPath": "dist/apps/jetstream-worker", @@ -13,7 +13,8 @@ "tsConfig": "apps/jetstream-worker/tsconfig.app.json", "assets": ["apps/jetstream-worker/src/assets"], "target": "node", - "compiler": "tsc" + "compiler": "tsc", + "webpackConfig": "apps/jetstream-worker/webpack.config.js" }, "configurations": { "production": { @@ -30,7 +31,7 @@ } }, "serve": { - "executor": "@nrwl/node:node", + "executor": "@nx/js:node", "options": { "buildTarget": "jetstream-worker:build", "inspect": true, @@ -38,18 +39,14 @@ } }, "lint": { - "executor": "@nrwl/linter:eslint", - "outputs": ["{options.outputFile}"], - "options": { - "lintFilePatterns": ["apps/jetstream-worker/**/*.ts"] - } + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"] }, "test": { - "executor": "@nrwl/jest:jest", + "executor": "@nx/jest:jest", "outputs": ["{workspaceRoot}/coverage/apps/jetstream-worker"], "options": { - "jestConfig": "apps/jetstream-worker/jest.config.ts", - "passWithNoTests": true + "jestConfig": "apps/jetstream-worker/jest.config.ts" } } }, diff --git a/apps/jetstream-worker/webpack.config.js b/apps/jetstream-worker/webpack.config.js new file mode 100644 index 000000000..0be4f4f03 --- /dev/null +++ b/apps/jetstream-worker/webpack.config.js @@ -0,0 +1,8 @@ +const { composePlugins, withNx } = require('@nx/webpack'); + +// Nx plugins for webpack. +module.exports = composePlugins(withNx(), (config) => { + // Note: This was added by an Nx migration. Webpack builds are required to have a corresponding Webpack config file. + // See: https://nx.dev/recipes/webpack/webpack-config-setup + return config; +}); diff --git a/apps/jetstream/.babelrc b/apps/jetstream/.babelrc index c00edfd4b..1bfb12338 100644 --- a/apps/jetstream/.babelrc +++ b/apps/jetstream/.babelrc @@ -1,4 +1,4 @@ { - "presets": [["@nrwl/react/babel", { "runtime": "automatic", "importSource": "@emotion/react" }]], + "presets": [["@nx/react/babel", { "runtime": "automatic", "importSource": "@emotion/react" }]], "plugins": ["@emotion/babel-plugin"] } diff --git a/apps/jetstream/.eslintrc.json b/apps/jetstream/.eslintrc.json index 0f2d446bf..a56f94ecc 100644 --- a/apps/jetstream/.eslintrc.json +++ b/apps/jetstream/.eslintrc.json @@ -284,7 +284,7 @@ } }, "plugins": ["import", "jsx-a11y", "react", "react-hooks"], - "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], + "extends": ["plugin:@nx/react", "../../.eslintrc.json"], "ignorePatterns": ["!**/*"], "overrides": [ { diff --git a/apps/jetstream/jest.config.ts b/apps/jetstream/jest.config.ts index 6412b5450..139c034cb 100644 --- a/apps/jetstream/jest.config.ts +++ b/apps/jetstream/jest.config.ts @@ -1,7 +1,7 @@ /* eslint-disable */ export default { transform: { - '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest', + '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest', '^.+\\.[tj]sx?$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'], diff --git a/apps/jetstream/project.json b/apps/jetstream/project.json index cbcf909e5..c7bbd1a63 100644 --- a/apps/jetstream/project.json +++ b/apps/jetstream/project.json @@ -6,7 +6,7 @@ "generators": {}, "targets": { "build": { - "executor": "@nrwl/vite:build", + "executor": "@nx/vite:build", "options": { "outputPath": "dist/apps/jetstream" }, @@ -120,7 +120,7 @@ "defaultConfiguration": "production" }, "serve": { - "executor": "@nrwl/vite:dev-server", + "executor": "@nx/vite:dev-server", "options": { "buildTarget": "jetstream:build", "hmr": true, @@ -139,31 +139,17 @@ "defaultConfiguration": "development" }, "lint": { - "executor": "@nrwl/linter:eslint", - "options": { - "lintFilePatterns": [ - "apps/jetstream/**/*.js", - "apps/jetstream/**/*.jsx", - "apps/jetstream/**/*.ts", - "apps/jetstream/**/*.tsx", - "apps/jetstream/**/*.spec.ts", - "apps/jetstream/**/*.spec.tsx", - "apps/jetstream/**/*.spec.js", - "apps/jetstream/**/*.spec.jsx", - "apps/jetstream/**/*.d.ts" - ] - } + "executor": "@nx/eslint:lint" }, "test": { - "executor": "@nrwl/jest:jest", + "executor": "@nx/jest:jest", "options": { - "jestConfig": "apps/jetstream/jest.config.ts", - "passWithNoTests": true + "jestConfig": "apps/jetstream/jest.config.ts" }, "outputs": ["{workspaceRoot}/coverage/apps/jetstream"] }, "preview": { - "executor": "@nrwl/vite:preview-server", + "executor": "@nx/vite:preview-server", "defaultConfiguration": "development", "options": { "buildTarget": "jetstream:build", diff --git a/apps/jetstream/src/app/app-state.ts b/apps/jetstream/src/app/app-state.ts index c206c683f..601b37197 100644 --- a/apps/jetstream/src/app/app-state.ts +++ b/apps/jetstream/src/app/app-state.ts @@ -191,6 +191,14 @@ export const salesforceOrgsOmitSelectedState = selector({ }, }); +export const selectSkipFrontdoorAuth = selector({ + key: 'selectSkipFrontdoorAuth', + get: ({ get }) => { + const userProfile = get(userProfileState); + return userProfile?.preferences?.skipFrontdoorLogin || false; + }, +}); + export const salesforceOrgsById = selector({ key: 'salesforceOrgsById', get: ({ get }) => { diff --git a/apps/jetstream/src/app/app.tsx b/apps/jetstream/src/app/app.tsx index 1b704cafb..65130876f 100644 --- a/apps/jetstream/src/app/app.tsx +++ b/apps/jetstream/src/app/app.tsx @@ -1,5 +1,5 @@ import { Maybe, UserProfileUi } from '@jetstream/types'; -import { ConfirmationServiceProvider } from '@jetstream/ui'; +import { AppToast, ConfirmationServiceProvider } from '@jetstream/ui'; // import { initSocket } from '@jetstream/shared/data'; import { OverlayProvider } from '@react-aria/overlays'; import { Suspense, useEffect, useState } from 'react'; @@ -13,7 +13,6 @@ import { AppRoutes } from './AppRoutes'; import AppInitializer from './components/core/AppInitializer'; import AppLoading from './components/core/AppLoading'; import AppStateResetOnOrgChange from './components/core/AppStateResetOnOrgChange'; -import AppToast from './components/core/AppToast'; import { DownloadFileStream } from './components/core/DownloadFileStream'; import ErrorBoundaryFallback from './components/core/ErrorBoundaryFallback'; import HeaderNavbar from './components/core/HeaderNavbar'; diff --git a/apps/jetstream/src/app/components/anonymous-apex/AnonymousApex.tsx b/apps/jetstream/src/app/components/anonymous-apex/AnonymousApex.tsx index 5c3b9a352..0701b4e5a 100644 --- a/apps/jetstream/src/app/components/anonymous-apex/AnonymousApex.tsx +++ b/apps/jetstream/src/app/components/anonymous-apex/AnonymousApex.tsx @@ -14,9 +14,12 @@ import { CopyToClipboard, Grid, Icon, + KeyboardShortcut, SalesforceLogin, Spinner, + Tooltip, ViewDocsLink, + getModifierKey, } from '@jetstream/ui'; import Editor, { OnMount, useMonaco } from '@monaco-editor/react'; import localforage from 'localforage'; @@ -24,7 +27,7 @@ import escapeRegExp from 'lodash/escapeRegExp'; import type { editor } from 'monaco-editor'; import { Fragment, FunctionComponent, MouseEvent, useCallback, useEffect, useRef, useState } from 'react'; import { useRecoilState, useRecoilValue } from 'recoil'; -import { STORAGE_KEYS, applicationCookieState, selectedOrgState } from '../../app-state'; +import { STORAGE_KEYS, applicationCookieState, selectSkipFrontdoorAuth, selectedOrgState } from '../../app-state'; import { useAmplitude } from '../core/analytics'; import AnonymousApexFilter from './AnonymousApexFilter'; import AnonymousApexHistory from './AnonymousApexHistory'; @@ -52,7 +55,8 @@ export const AnonymousApex: FunctionComponent = () => { const logRef = useRef(); const { trackEvent } = useAmplitude(); const rollbar = useRollbar(); - const [{ serverUrl }] = useRecoilState(applicationCookieState); + const { serverUrl } = useRecoilValue(applicationCookieState); + const skipFrontDoorAuth = useRecoilValue(selectSkipFrontdoorAuth); const selectedOrg = useRecoilValue(selectedOrgState); const [apex, setApex] = useState(() => localStorage.getItem(STORAGE_KEYS.ANONYMOUS_APEX_STORAGE_KEY) || ''); const [results, setResults] = useState(''); @@ -143,7 +147,6 @@ export const AnonymousApex: FunctionComponent = () => { setLoading(true); setResults(''); setTextFilter(''); - setUserDebug(false); logRef.current?.revealLine(1); setResultsStatus({ hasResults: false, success: false, label: null }); try { @@ -171,7 +174,7 @@ export const AnonymousApex: FunctionComponent = () => { }) .catch((ex) => { logger.warn('[ERROR] Could not save history', ex); - rollbar.error('Error saving apex history', ex); + rollbar.error('Error saving apex history', { message: ex.message, stack: ex.stack }); }); } trackEvent(ANALYTICS_KEYS.apex_Submitted, { success: result.success }); @@ -212,10 +215,6 @@ export const AnonymousApex: FunctionComponent = () => { window.open(loginUrl, 'Developer Console', 'height=600,width=600'); } - function handleLogLevelChange(event: React.ChangeEvent) { - setLogLevel(event.target.value); - } - return ( = () => {
Anonymous Apex
@@ -252,10 +252,19 @@ export const AnonymousApex: FunctionComponent = () => { onSelected={(item) => setLogLevel(item.id)} />
- + + +
+ } + > + + } > @@ -265,6 +274,7 @@ export const AnonymousApex: FunctionComponent = () => { className="slds-m-right_x-small" serverUrl={serverUrl} org={selectedOrg} + skipFrontDoorAuth={skipFrontDoorAuth} returnUrl="/_ui/common/apex/debug/ApexCSIPage" omitIcon title="Open developer console" @@ -289,6 +299,7 @@ export const AnonymousApex: FunctionComponent = () => {
Results diff --git a/apps/jetstream/src/app/components/automation-control/AutomationControlEditor.tsx b/apps/jetstream/src/app/components/automation-control/AutomationControlEditor.tsx index 2e2368f36..9b2d52f52 100644 --- a/apps/jetstream/src/app/components/automation-control/AutomationControlEditor.tsx +++ b/apps/jetstream/src/app/components/automation-control/AutomationControlEditor.tsx @@ -29,8 +29,8 @@ import { import classNames from 'classnames'; import { FunctionComponent, useState } from 'react'; import { Link } from 'react-router-dom'; -import { useRecoilState, useRecoilValue } from 'recoil'; -import { applicationCookieState, selectedOrgState } from '../../app-state'; +import { useRecoilValue } from 'recoil'; +import { applicationCookieState, selectSkipFrontdoorAuth, selectedOrgState } from '../../app-state'; import { RequireMetadataApiBanner } from '../core/RequireMetadataApiBanner'; import { useAmplitude } from '../core/analytics'; import * as fromJetstreamEvents from '../core/jetstream-events'; @@ -60,7 +60,9 @@ export const AutomationControlEditor: FunctionComponent(selectedOrgState); - const [{ serverUrl, defaultApiVersion, google_apiKey, google_appId, google_clientId }] = useRecoilState(applicationCookieState); + const { serverUrl, defaultApiVersion, google_apiKey, google_appId, google_clientId } = useRecoilValue(applicationCookieState); + const skipFrontdoorLogin = useRecoilValue(selectSkipFrontdoorAuth); + const selectedSObjects = useRecoilValue(fromAutomationCtlState.selectedSObjectsState); const selectedAutomationTypes = useRecoilValue(fromAutomationCtlState.selectedAutomationTypes); @@ -368,6 +370,7 @@ export const AutomationControlEditor: FunctionComponent [] = [ { name: 'Old Value', key: 'isActiveInitialState', - formatter: BooleanAndVersionRenderer, + renderCell: BooleanAndVersionRenderer, width: 130, cellClass: 'bg-color-gray', }, { name: 'New Value', key: 'isActive', - formatter: BooleanAndVersionRenderer, + renderCell: BooleanAndVersionRenderer, width: 130, cellClass: 'active-item-yellow-bg', }, { name: 'Status', key: 'status', - formatter: AutomationDeployStatusRenderer, + renderCell: AutomationDeployStatusRenderer, width: 200, }, ]; diff --git a/apps/jetstream/src/app/components/automation-control/AutomationControlEditorTable.tsx b/apps/jetstream/src/app/components/automation-control/AutomationControlEditorTable.tsx index c6e54f34a..44b9c7925 100644 --- a/apps/jetstream/src/app/components/automation-control/AutomationControlEditorTable.tsx +++ b/apps/jetstream/src/app/components/automation-control/AutomationControlEditorTable.tsx @@ -1,13 +1,12 @@ import { SalesforceOrgUi } from '@jetstream/types'; import { ColumnWithFilter, DataTable, setColumnFromType } from '@jetstream/ui'; import { forwardRef, useMemo } from 'react'; -import { RowHeightArgs } from 'react-data-grid'; import { isTableRow } from './automation-control-data-utils'; import { AdditionalDetailRenderer, ExpandingLabelRenderer, LoadingAndActiveRenderer } from './automation-control-table-renderers'; import { TableRowOrItemOrChild } from './automation-control-types'; -const getRowHeight = ({ row, type }: RowHeightArgs) => { - if (type === 'GROUP' || isTableRow(row)) { +const getRowHeight = (row: TableRowOrItemOrChild) => { + if (isTableRow(row)) { return 28.5; } if (row.additionalData.length > 1) { @@ -27,6 +26,7 @@ const getRowId = ({ key }: TableRowOrItemOrChild) => key; export interface AutomationControlEditorTableProps { serverUrl: string; + skipFrontdoorLogin: boolean; selectedOrg: SalesforceOrgUi; rows: TableRowOrItemOrChild[]; quickFilterText?: string | null; @@ -35,7 +35,7 @@ export interface AutomationControlEditorTableProps { } export const AutomationControlEditorTable = forwardRef( - ({ serverUrl, selectedOrg, rows, quickFilterText, toggleRowExpand, updateIsActiveFlag }, ref) => { + ({ serverUrl, skipFrontdoorLogin, selectedOrg, rows, quickFilterText, toggleRowExpand, updateIsActiveFlag }, ref) => { const columns = useMemo(() => { return [ { @@ -43,7 +43,7 @@ export const AutomationControlEditorTable = forwardRef { + renderCell: ({ column, row }) => { return ( (!isTableRow(row) && row.isActive !== row.isActiveInitialState ? 'active-item-yellow-bg' : ''), - formatter: ({ row }) => { + renderCell: ({ row }) => { return ; }, }, @@ -85,10 +85,10 @@ export const AutomationControlEditorTable = forwardRef[]; }, []); @@ -97,6 +97,7 @@ export const AutomationControlEditorTable = forwardRef + {value} ) : ( @@ -104,7 +104,7 @@ export const LoadingAndActiveRenderer: FunctionComponent<{ } }; -export const AdditionalDetailRenderer: FunctionComponent> = ({ row }) => { +export const AdditionalDetailRenderer: FunctionComponent> = ({ row }) => { if (!isTableRow(row) && Array.isArray(row.additionalData) && row.additionalData.length > 0) { return ( @@ -132,7 +132,7 @@ export const AdditionalDetailRenderer: FunctionComponent> = ({ column, row }) => { +export const BooleanAndVersionRenderer: FunctionComponent> = ({ column, row }) => { const metadata = row; const type = metadata.type; const value = metadata[column.key]; @@ -182,7 +182,7 @@ function getErrorMessageContentString(deployError: Maybe> = ({ row }) => { +export const AutomationDeployStatusRenderer: FunctionComponent> = ({ row }) => { const { status, deploy } = row; const { deployError } = deploy; const isLoading = loadingStatuses.includes(status); diff --git a/apps/jetstream/src/app/components/core/AppStateResetOnOrgChange.tsx b/apps/jetstream/src/app/components/core/AppStateResetOnOrgChange.tsx index f2e80ec05..246484309 100644 --- a/apps/jetstream/src/app/components/core/AppStateResetOnOrgChange.tsx +++ b/apps/jetstream/src/app/components/core/AppStateResetOnOrgChange.tsx @@ -1,5 +1,5 @@ import { SalesforceOrgUi } from '@jetstream/types'; -import { Fragment, FunctionComponent, useEffect, useState } from 'react'; +import { FunctionComponent, useEffect, useState } from 'react'; import { Resetter, useRecoilValue, useResetRecoilState } from 'recoil'; import * as fromAppState from '../../app-state'; import * as fromAutomationControlState from '../automation-control/automation-control.state'; @@ -49,6 +49,7 @@ export const AppStateResetOnOrgChange: FunctionComponent; + return null; }; export default AppStateResetOnOrgChange; diff --git a/apps/jetstream/src/app/components/core/EmailSupport.tsx b/apps/jetstream/src/app/components/core/EmailSupport.tsx index c8afe3917..1ce5decdb 100644 --- a/apps/jetstream/src/app/components/core/EmailSupport.tsx +++ b/apps/jetstream/src/app/components/core/EmailSupport.tsx @@ -1,9 +1,8 @@ import { emailSupport } from '@jetstream/shared/data'; import { useRollbar } from '@jetstream/shared/ui-utils'; import { InputReadFileContent } from '@jetstream/types'; -import { FileSelector, Icon, ScopedNotification, Spinner, Textarea } from '@jetstream/ui'; +import { FileSelector, Icon, ScopedNotification, Spinner, Textarea, fireToast } from '@jetstream/ui'; import { useState } from 'react'; -import { fireToast } from './AppToast'; const MAX_ATTACHMENTS = 3; diff --git a/apps/jetstream/src/app/components/core/ErrorBoundaryFallback.tsx b/apps/jetstream/src/app/components/core/ErrorBoundaryFallback.tsx index ba8f79823..04e65bb29 100644 --- a/apps/jetstream/src/app/components/core/ErrorBoundaryFallback.tsx +++ b/apps/jetstream/src/app/components/core/ErrorBoundaryFallback.tsx @@ -23,7 +23,7 @@ export const ErrorBoundaryFallback: FunctionComponent = ({ error, }); } catch (ex) { try { - rollbar.error('An unknown error occurred logging error event'); + rollbar.error('An unknown error occurred logging error event', { message: ex.message, stack: ex.stack }); } catch (ex) { logger.error('Error logging event to rollbar'); logger.error(ex); diff --git a/apps/jetstream/src/app/components/core/HeaderDonatePopover.tsx b/apps/jetstream/src/app/components/core/HeaderDonatePopover.tsx index 67af3e76a..4d30179c4 100644 --- a/apps/jetstream/src/app/components/core/HeaderDonatePopover.tsx +++ b/apps/jetstream/src/app/components/core/HeaderDonatePopover.tsx @@ -32,7 +32,7 @@ export const HeaderDonatePopover: FunctionComponent = } content={
-

Jetstream is an open source project and is paid for and supported by volunteers.

+

Jetstream is a source-available project and is paid for and supported by the community.

= ({ userProfile logo={isElectron ? : Logo} orgs={} userMenuItems={userMenuItems} - rightHandMenuItems={[, , , ]} + rightHandMenuItems={[, , , ]} isElectron={isElectron} onUserMenuItemSelected={handleUserMenuSelection} > diff --git a/apps/jetstream/src/app/components/core/ViewChildRecords.tsx b/apps/jetstream/src/app/components/core/ViewChildRecords.tsx new file mode 100644 index 000000000..3f57221fc --- /dev/null +++ b/apps/jetstream/src/app/components/core/ViewChildRecords.tsx @@ -0,0 +1,369 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { css } from '@emotion/react'; +import { logger } from '@jetstream/shared/client-logger'; +import { ANALYTICS_KEYS, SOBJECT_NAME_FIELD_MAP } from '@jetstream/shared/constants'; +import { queryAll, queryAllFromList, queryAllWithCache } from '@jetstream/shared/data'; +import { getMapOf, splitArrayToMaxSize } from '@jetstream/shared/utils'; +import { Maybe, Record, SalesforceOrgUi } from '@jetstream/types'; +import { + AutoFullHeightContainer, + ColumnWithFilter, + DataTree, + Grid, + Icon, + PopoverErrorButton, + SalesforceLogin, + ScopedNotification, + Spinner, + Tooltip, + setColumnFromType, +} from '@jetstream/ui'; +import type { ChildRelationship, QueryResult } from 'jsforce'; +import groupBy from 'lodash/groupBy'; +import { FunctionComponent, MutableRefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useRecoilValue } from 'recoil'; +import { composeQuery, getField } from 'soql-parser-js'; +import { applicationCookieState, selectSkipFrontdoorAuth } from '../../app-state'; +import { useAmplitude } from './analytics'; + +function getRowId(row: Record): string { + return `${row.Id}-${row._idx}`; +} + +const groupedRows = ['_groupByLabel'] as const; + +function getRows(childRelationships: ChildRelationship[], record: Record) { + return (childRelationships || []) + .flatMap((childRelationship): Record[] => { + if (childRelationship.relationshipName && record[childRelationship.relationshipName]) { + const childQueryResults: QueryResult = record[childRelationship.relationshipName]; + return childQueryResults.records.map((record, i) => ({ + _groupByLabel: `${childRelationship.childSObject} (${childRelationship.relationshipName})`, + _record: record._record, + _idx: i, + Id: record.Id, + Name: record[SOBJECT_NAME_FIELD_MAP[childRelationship.childSObject] || 'Name'], + CreatedDate: record.CreatedDate, + CreatedByName: record.CreatedBy?.Name || 'unknown', + LastModifiedDate: record.LastModifiedDate, + LastModifiedByName: record.LastModifiedBy?.Name || 'unknown', + })); + } + return []; // allow type inference to work + }) + .filter(Boolean); +} + +interface ChildRecordRow { + _groupByLabel: string; // sobject + relationship name + _record: any; // original record + _idx: number; + Name: string; + CreatedDate: string; + CreatedByName: string; + LastModifiedDate: string; + LastModifiedByName: string; +} + +export interface ViewChildRecordsProps { + selectedOrg: SalesforceOrgUi; + sobjectName: string; + parentRecordId: string; + initialData?: Record; + childRelationships: ChildRelationship[]; + modalRef?: MutableRefObject>; + onChildrenData?: (parentRecordId: string, record: Record) => void; +} + +export const ViewChildRecords: FunctionComponent = ({ + selectedOrg, + sobjectName, + parentRecordId, + initialData, + childRelationships, + modalRef, + onChildrenData, +}) => { + const { trackEvent } = useAmplitude(); + const isMounted = useRef(true); + const { serverUrl } = useRecoilValue(applicationCookieState); + const skipFrontDoorAuth = useRecoilValue(selectSkipFrontdoorAuth); + const [loading, setLoading] = useState(true); + const [rows, setRows] = useState[]>([]); + const [expandedGroupIds, setExpandedGroupIds] = useState(new Set()); + const [fetchErrors, setHasFetchErrors] = useState([]); + + const columns = useMemo( + (): ColumnWithFilter>[] => [ + { + ...setColumnFromType>('_groupByLabel', 'text'), + key: '_groupByLabel', + name: '', + width: 40, + frozen: true, + renderGroupCell: ({ isExpanded }) => ( + + + + ), + }, + { + ...setColumnFromType('Id', 'text'), + key: 'Id', + name: 'Id', + renderCell: ({ row }) => { + return ( + + + {row.Id} + + + ); + }, + renderGroupCell: ({ toggleGroup, groupKey, childRows }) => ( + + ), + }, + { + ...setColumnFromType('Name', 'text'), + key: 'Name', + name: 'Name', + }, + { + ...setColumnFromType('LastModifiedByName', 'text'), + key: 'LastModifiedByName', + name: 'Modified By', + }, + { + ...setColumnFromType('LastModifiedDate', 'date'), + key: 'LastModifiedDate', + name: 'Last Modified', + }, + { + ...setColumnFromType('CreatedByName', 'date'), + key: 'CreatedByName', + name: 'Created By', + }, + { + ...setColumnFromType('CreatedDate', 'date'), + key: 'CreatedDate', + name: 'Created', + }, + ], + [selectedOrg, serverUrl, skipFrontDoorAuth] + ); + + const fetchChildRecords = useCallback( + async (skipCache?: boolean) => { + try { + setRows([]); + setExpandedGroupIds(new Set()); + setLoading(true); + // Some child objects are missing basic fields like Name and CreatedDate, and others are not queryable + // This fetches all the fields so the subqueries can be constructed with valid fields + const fields = ['Id', 'Name', 'CreatedDate', 'CreatedById', 'LastModifiedDate', 'LastModifiedById'].concat( + Array.from(new Set(Object.values(SOBJECT_NAME_FIELD_MAP))) + ); + const entityParticleQueries = splitArrayToMaxSize(Array.from(new Set(childRelationships.map((item) => item.childSObject))), 50).map( + (childRelationshipObjects) => + composeQuery({ + sObject: 'EntityDefinition', + fields: [ + getField('Id'), + getField('QualifiedApiName'), + getField({ + subquery: { + relationshipName: 'Fields', + fields: [getField('Id'), getField('QualifiedApiName')], + where: { + left: { + field: 'QualifiedApiName', + operator: 'IN', + value: fields, + literalType: 'STRING', + }, + }, + }, + }), + ], + where: { + left: { + field: 'QualifiedApiName', + operator: 'IN', + value: childRelationshipObjects, + literalType: 'STRING', + }, + operator: 'AND', + right: { + left: { + field: 'IsQueryable', + operator: '=', + value: 'true', + literalType: 'BOOLEAN', + }, + }, + }, + }) + ); + + const entityQueryResults = await queryAllFromList<{ + Id: string; + QualifiedApiName: string; + Fields: QueryResult<{ Id: string; QualifiedApiName: string }> | null; + }>(selectedOrg, entityParticleQueries); + + const queryResultsByObject = getMapOf(entityQueryResults.queryResults.records, 'QualifiedApiName'); + + const subqueries = childRelationships + .filter((item) => item.relationshipName && queryResultsByObject[item.childSObject]?.Fields?.records.length) + .map((childRelationship) => { + const fields = new Set( + queryResultsByObject[childRelationship.childSObject]?.Fields?.records?.map?.((record) => record.QualifiedApiName) || [] + ); + if (fields.has('CreatedById')) { + fields.add('CreatedBy.Name'); + } + if (fields.has('LastModifiedById')) { + fields.add('LastModifiedBy.Name'); + } + return getField({ + subquery: { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + relationshipName: childRelationship.relationshipName!, + fields: Array.from(fields).map((field) => getField(field)), + }, + }); + }); + + // combine all results with subqueries into this one record + let record = {}; + + for (const subquery of splitArrayToMaxSize(subqueries, 11)) { + try { + const query = composeQuery({ + sObject: sobjectName, + fields: [getField('Id'), getField(SOBJECT_NAME_FIELD_MAP[sobjectName] || 'Name'), ...subquery], + where: { + left: { + field: 'Id', + operator: '=', + value: parentRecordId, + literalType: 'STRING', + }, + }, + }); + + const { queryResults } = skipCache ? await queryAll(selectedOrg, query) : (await queryAllWithCache(selectedOrg, query)).data; + if (!isMounted.current) { + return; + } + record = { ...record, ...queryResults.records[0] }; + } catch (ex) { + setHasFetchErrors((existing) => [...existing, ex.message]); + logger.warn('Error querying child records', { ex }); + } + } + + logger.log({ childRelationships, record }); + + const _rows = getRows(childRelationships, record); + setRows(_rows); + setExpandedGroupIds(new Set(_rows.map((row) => row._groupByLabel))); + + onChildrenData && onChildrenData(parentRecordId, record); + trackEvent(ANALYTICS_KEYS.record_modal_view_children, { subqueryCount: subqueries.length, childRecordCount: _rows }); + } catch (ex) { + logger.warn('Error loading records', ex); + setHasFetchErrors((existing) => [...existing, ex.message]); + } finally { + setLoading(false); + } + }, + [childRelationships, onChildrenData, parentRecordId, selectedOrg, sobjectName, trackEvent] + ); + + useEffect(() => { + if (initialData) { + const _rows = getRows(childRelationships, initialData); + setRows(_rows); + setExpandedGroupIds(new Set(_rows.map((row) => row._groupByLabel))); + setLoading(false); + } else { + fetchChildRecords(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [fetchChildRecords]); + + if (loading) { + return ( + + + + ); + } + + return ( + <> + {!!fetchErrors.length && ( + + There was an error fetching some child records. + + )} + + + + + + + setExpandedGroupIds(items)} + context={{ portalRefForFilters: modalRef }} + /> + + + ); +}; + +export default ViewChildRecords; diff --git a/apps/jetstream/src/app/components/core/ViewEditCloneRecord.tsx b/apps/jetstream/src/app/components/core/ViewEditCloneRecord.tsx index fb6d6264c..a2ab11399 100644 --- a/apps/jetstream/src/app/components/core/ViewEditCloneRecord.tsx +++ b/apps/jetstream/src/app/components/core/ViewEditCloneRecord.tsx @@ -2,10 +2,16 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { mockPicklistValuesFromSobjectDescribe, UiRecordForm } from '@jetstream/record-form'; import { logger } from '@jetstream/shared/client-logger'; -import { describeSObject, genericRequest, sobjectOperation } from '@jetstream/shared/data'; -import { isErrorResponse, useNonInitialEffect } from '@jetstream/shared/ui-utils'; +import { ANALYTICS_KEYS, SOBJECT_NAME_FIELD_MAP } from '@jetstream/shared/constants'; +import { describeGlobal, describeSObject, genericRequest, query, sobjectOperation } from '@jetstream/shared/data'; +import { copyRecordsToClipboard, isErrorResponse, useNonInitialEffect } from '@jetstream/shared/ui-utils'; import { + AsyncJobNew, + BulkDownloadJob, CloneEditView, + FileExtCsvXLSXJsonGSheet, + MapOf, + Maybe, PicklistFieldValues, PicklistFieldValuesResponse, Record, @@ -13,14 +19,29 @@ import { SalesforceOrgUi, SobjectCollectionResponse, } from '@jetstream/types'; -import { FileDownloadModal, Grid, Icon, Modal, PopoverErrorButton, Spinner } from '@jetstream/ui'; +import { + Breadcrumbs, + ButtonGroupContainer, + DownloadFromServerOpts, + DropDown, + Grid, + Icon, + Modal, + PopoverErrorButton, + RecordDownloadModal, + SalesforceLogin, + Spinner, + Tabs, +} from '@jetstream/ui'; import Editor from '@monaco-editor/react'; -import copyToClipboard from 'copy-to-clipboard'; -import type { Field } from 'jsforce'; -import isUndefined from 'lodash/isUndefined'; +import type { ChildRelationship, Field } from 'jsforce'; +import isNumber from 'lodash/isNumber'; +import isObject from 'lodash/isObject'; import { Fragment, FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'; import { useRecoilState } from 'recoil'; +import { composeQuery, getField } from 'soql-parser-js'; import { applicationCookieState } from '../../app-state'; +import * as fromJetstreamEvents from '../core/jetstream-events'; import { combineRecordsForClone, EditFromErrors, @@ -28,7 +49,19 @@ import { transformEditForm, validateEditForm, } from '../query/utils/query-utils'; -import * as fromJetstreamEvents from './jetstream-events'; +import { useAmplitude } from './analytics'; +import ViewChildRecords from './ViewChildRecords'; + +const CHILD_RELATIONSHIP_BLOCK_LIST = new Set([ + 'OutgoingEmailRelations', + 'UserFieldAccessRights', + 'RecordActionHistories', + 'DocumentChecklistItemUploadedBy', + 'DocumentChecklistWho', + 'UserEntityAccessRights', // this works, but no useful fields show up + 'UserPreferences', // this works, but no useful fields show up + 'ContentDocumentLinks', // this works, but no useful fields show up +]); function getModalTitle(action: CloneEditView) { if (action === 'view') { @@ -41,11 +74,39 @@ function getModalTitle(action: CloneEditView) { return 'Create Record'; } -function getTagline(sobjectName: string, initialRecord?: Record, recordId?: string) { +function getTagline( + selectedOrg: SalesforceOrgUi, + serverUrl: string, + sobjectName: string, + initialRecord?: Record, + recordId?: string | null +) { if (initialRecord && recordId) { - return `${sobjectName} - ${initialRecord.Name} - ${recordId}`; + return ( + + {sobjectName} - {initialRecord[SOBJECT_NAME_FIELD_MAP[sobjectName] || 'Name']} - {recordId} + + ); } else if (recordId) { - return `${sobjectName} - ${recordId}`; + return ( + + {sobjectName} - {recordId} + + ); } return sobjectName; } @@ -55,42 +116,57 @@ export interface ViewEditCloneRecordProps { selectedOrg: SalesforceOrgUi; action: CloneEditView; sobjectName: string; - recordId: string; + recordId: string | null; onClose: (reloadRecords?: boolean) => void; onChangeAction: (action: CloneEditView) => void; onFetch?: (recordId: string, record: any) => void; onFetchError?: (recordId: string, sobjectName: string) => void; } -/** - * TODO: convert some use-effects to useReducer and maybe a custom hook to manage the interactions - */ export const ViewEditCloneRecord: FunctionComponent = ({ apiVersion, selectedOrg, action, - sobjectName, - recordId, + sobjectName: initialSobjectName, + recordId: initialRecordId, onClose, onChangeAction, onFetch, onFetchError, }) => { + const { trackEvent } = useAmplitude(); const isMounted = useRef(true); - const [{ google_apiKey, google_appId, google_clientId }] = useRecoilState(applicationCookieState); + const modalRef = useRef(null); + const modalBodyRef = useRef(null); + // If user was ever in view mode, clicking cancel will take back to view instead of close + const hasEverBeenInViewMode = useRef(false); + hasEverBeenInViewMode.current = action === 'view' || hasEverBeenInViewMode.current; + + const [{ serverUrl, google_apiKey, google_appId, google_clientId }] = useRecoilState(applicationCookieState); + + // User can drill in to related records, this allows us to go back up the chain via Breadcrumbs + const [recordId, setRecordId] = useState(initialRecordId); + const [sobjectName, setSobjectName] = useState(initialSobjectName); + const [priorRecords, setPriorRecords] = useState<{ recordId: string; sobjectName: string }[]>([]); + + const [childRelationships, setChildRelationships] = useState(); const [sobjectFields, setSobjectFields] = useState(); const [picklistValues, setPicklistValues] = useState(); const [initialRecord, setInitialRecord] = useState(); - const [modifiedRecord, setModifiedRecord] = useState(); + const [recordWithChildrenQueries, setRecordWithChildrenQueries] = useState>>({}); + const [modifiedRecord, setModifiedRecord] = useState({}); const [formIsDirty, setIsFormDirty] = useState(false); const [loading, setLoading] = useState(false); const [saving, setSaving] = useState(false); - const [showFieldTypes, setShowFieldTypes] = useState(false); const [formErrors, setFormErrors] = useState({ hasErrors: false, fieldErrors: {}, generalErrors: [] }); const [modalTitle, setModalTitle] = useState(() => getModalTitle(action)); const [isViewAsJson, setIsViewAsJson] = useState(false); - const [downloadModalOpen, setDownloadModalOpen] = useState(false); + const [downloadModalData, setDownloadModalData] = useState< + { open: false } | { open: true; data: Record; fields: string[]; subqueryFields?: MapOf } + >({ + open: false, + }); useEffect(() => { isMounted.current = true; @@ -105,37 +181,25 @@ export const ViewEditCloneRecord: FunctionComponent = }, [action]); useNonInitialEffect(() => { - const isDirty = Object.values(modifiedRecord).filter((value) => value !== undefined).length > 0; - const fieldErrors = { ...formErrors.fieldErrors }; - let needsFormErrorsUpdate = false; - let hasRemainingErrors = false; - if (formErrors.hasErrors) { - Object.keys(fieldErrors).forEach((key) => { - if (isUndefined(modifiedRecord[key])) { - needsFormErrorsUpdate = true; - fieldErrors[key] = undefined; - } else { - hasRemainingErrors = true; - } - }); - } - setIsFormDirty(isDirty); - if (needsFormErrorsUpdate) { - setFormErrors({ hasErrors: hasRemainingErrors, fieldErrors, generalErrors: formErrors.generalErrors }); - } - }, [modifiedRecord]); + setIsFormDirty(Object.values(modifiedRecord).filter((value) => value !== undefined).length > 0); + }, [action, modifiedRecord]); const fetchMetadata = useCallback(async () => { try { let picklistValues: PicklistFieldValues = {}; let record: Record = {}; - if (action !== 'create') { + const sobjectMetadata = await describeSObject(selectedOrg, sobjectName); + setChildRelationships( + sobjectMetadata.data.childRelationships.filter( + (item) => item.relationshipName && item.childSObject && !CHILD_RELATIONSHIP_BLOCK_LIST.has(item.relationshipName) + ) + ); + + if (action !== 'create' && recordId) { record = await sobjectOperation(selectedOrg, sobjectName, 'retrieve', { ids: recordId }); } - const sobjectMetadata = await describeSObject(selectedOrg, sobjectName); - let recordTypeId = record?.RecordTypeId; if (!recordTypeId) { const recordTypeInfos = sobjectMetadata.data.recordTypeInfos; @@ -170,6 +234,38 @@ export const ViewEditCloneRecord: FunctionComponent = picklistValues = mockPicklistValuesFromSobjectDescribe(sobjectMetadata.data); } + // Query all related records so that related record name can be shown in the UI + if (recordId) { + try { + const { queryResults } = await query( + selectedOrg, + composeQuery({ + sObject: sobjectName, + fields: sobjectMetadata.data.fields + .filter((field) => field.type === 'reference' && field.relationshipName && record[field.name]) + .map((field) => + getField({ + field: 'Name', + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + relationships: [field.relationshipName!], + }) + ), + where: { + left: { + field: 'Id', + operator: '=', + value: recordId, + literalType: 'STRING', + }, + }, + }) + ); + record = { ...record, ...queryResults.records[0] }; + } catch (ex) { + logger.warn('Could not fetch related records'); + } + } + if (action === 'clone') { record.attributes = undefined; record.Id = undefined; @@ -180,10 +276,9 @@ export const ViewEditCloneRecord: FunctionComponent = setPicklistValues(picklistValues); setInitialRecord(record); setLoading(false); - onFetch && onFetch(recordId, record); + onFetch && recordId && onFetch(recordId, record); } } catch (ex) { - // TODO: error handling if (isMounted.current) { setFormErrors({ hasErrors: true, @@ -191,7 +286,7 @@ export const ViewEditCloneRecord: FunctionComponent = generalErrors: ['Oops. There was a problem loading the record information. Make sure the record id is valid.'], }); setLoading(false); - onFetchError && onFetchError(recordId, sobjectName); + onFetchError && recordId && onFetchError(recordId, sobjectName); } } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -202,6 +297,10 @@ export const ViewEditCloneRecord: FunctionComponent = fetchMetadata(); }, [fetchMetadata]); + useEffect(() => { + trackEvent(ANALYTICS_KEYS.record_modal_action_change, { action, isViewAsJson }); + }, [action, isViewAsJson, trackEvent]); + async function handleRecordChange(record: Record) { setModifiedRecord(record); } @@ -237,7 +336,8 @@ export const ViewEditCloneRecord: FunctionComponent = setFormErrors(handleEditFormErrorResponse(recordResponse)); } else { // record created/updated - onClose(true); + hasEverBeenInViewMode.current ? onChangeAction('view') : onClose(true); + // onClose(true); } } } catch (ex) { @@ -248,40 +348,224 @@ export const ViewEditCloneRecord: FunctionComponent = if (isMounted.current) { setSaving(false); } + trackEvent(ANALYTICS_KEYS.record_modal_save, { action }); + } + + function handleDownloadModalOpen() { + const record = { ...initialRecord, ...recordWithChildrenQueries }; + setDownloadModalData({ + open: true, + data: [record], + fields: Object.keys(record).filter((field) => field !== 'attributes'), + subqueryFields: Object.keys(record) + .filter((field) => field !== 'attributes') + .reduce((acc, field) => { + if (record[field] && isObject(record[field]) && Array.isArray(record[field]?.records)) { + const firstRecord = record[field]?.records?.[0] || {}; + acc[field] = Object.keys(firstRecord) + .filter((field) => field !== 'attributes') + .map((field) => { + if (field === 'CreatedBy' || field === 'LastModifiedBy') { + return `${field}.Name`; + } + return field; + }); + } + return acc; + }, {}), + }); } function handleDownloadModalClose(canceled?: boolean) { - if (canceled) { - setDownloadModalOpen(false); - } else { - onClose(); + setDownloadModalData({ open: false }); + } + + // Used for Google Drive upload + function handleDownloadFromServer(options: DownloadFromServerOpts) { + const { fileFormat, fileName, fields, subqueryFields, includeSubquery, whichFields, recordsToInclude, googleFolder } = options; + const jobs: AsyncJobNew[] = [ + { + type: 'BulkDownload', + title: `Download Records`, + org: selectedOrg, + meta: { + serverUrl, + sObject: sobjectName, + soql: '', + isTooling: false, + includeDeletedRecords: false, + useBulkApi: false, + fields, + subqueryFields, + records: recordsToInclude || [], + totalRecordCount: 1, + nextRecordsUrl: '', + hasAllRecords: true, + fileFormat, + fileName, + includeSubquery, + googleFolder, + }, + }, + ]; + fromJetstreamEvents.emit({ type: 'newJob', payload: jobs }); + trackEvent(ANALYTICS_KEYS.record_modal_download, { + fileFormat, + whichFields, + includeSubquery, + }); + onClose(); + } + + function handleDidDownload(fileFormat: FileExtCsvXLSXJsonGSheet, whichFields: 'all' | 'specified', includeSubquery: boolean) { + trackEvent(ANALYTICS_KEYS.record_modal_download, { + fileFormat, + whichFields, + includeSubquery, + }); + } + + function handleCopyToClipboard(format: 'excel' | 'json' = 'excel') { + if (!initialRecord) { + return; } + copyRecordsToClipboard( + [initialRecord], + format, + Object.keys(initialRecord).filter((field) => field !== 'attributes') + ); + trackEvent(ANALYTICS_KEYS.record_modal_clipboard); } - function handleCopyToClipboard() { - copyToClipboard(initialRecord, { format: 'text/plain' }); + /** + * Drill in to related record + * Save breadcrumb of prior record so that user can navigate back + */ + async function viewRelatedRecord(newRecordId: string, metadata: Field) { + try { + const keyPrefix = newRecordId.substring(0, 3); + const describeGlobalResults = await describeGlobal(selectedOrg); + const sobject = describeGlobalResults.data.sobjects.find((sobject) => sobject.keyPrefix === keyPrefix); + if (!sobject) { + throw new Error(`Could not find sobject for record id ${newRecordId}`); + } + if (isMounted.current) { + recordId && setPriorRecords((prevValue) => [...prevValue, { recordId, sobjectName }]); + setModifiedRecord({}); + setRecordId(newRecordId); + setSobjectName(sobject.name); + } + } catch (ex) { + if (isMounted.current) { + setFormErrors({ + hasErrors: true, + fieldErrors: {}, + generalErrors: ['Oops. There was a problem loading the related record information.'], + }); + } + } finally { + trackEvent(ANALYTICS_KEYS.record_modal_view_related); + } + } + + /** + * User clicked on breadcrumb to revert to prior record + */ + function revertToPriorRecord(index: number) { + try { + const { recordId, sobjectName } = priorRecords[index]; + setPriorRecords((prevValue) => prevValue.filter((record, idx) => idx < index)); + setModifiedRecord({}); + setRecordId(recordId); + setSobjectName(sobjectName); + } catch (ex) { + setFormErrors({ + hasErrors: true, + fieldErrors: {}, + generalErrors: ['Oops. There was a problem loading the related record information.'], + }); + } + } + + const tabs = [ + { + id: 'root', + title: 'Record', + content: ( + + ), + }, + ]; + if (recordId) { + tabs.push({ + id: 'child', + title: 'Child Records', + content: ( + + setRecordWithChildrenQueries((priorData) => ({ ...priorData, [parentRecordId]: record })) + } + /> + ), + }); } return (
- {downloadModalOpen && ( - )} - {!downloadModalOpen && ( + + {!downloadModalData.open && ( + {getTagline(selectedOrg, serverUrl, sobjectName, initialRecord, recordId)} + {!!priorRecords.length && ( + <> +
+ ({ + id: `${item.sobjectName}-${item.recordId}-${index}`, + label: `${item.sobjectName} (${item.recordId})`, + metadata: index, + }))} + currentItem="Current Record" + onClick={(item) => isNumber(item.metadata) && revertToPriorRecord(item.metadata)} + /> + + )} +
+ } size="lg" closeOnEsc={!loading && !formIsDirty} className="h-100 slds-is-relative" @@ -291,30 +575,46 @@ export const ViewEditCloneRecord: FunctionComponent =
{formErrors.hasErrors && formErrors.generalErrors.length > 0 && ( - + )} - - {!isViewAsJson && ( + - )} + handleCopyToClipboard(item as 'excel' | 'json')} + /> + + @@ -362,29 +668,44 @@ export const ViewEditCloneRecord: FunctionComponent = } onClose={() => onClose()} > -
+
{(loading || saving) && } - {!loading && initialRecord && !isViewAsJson && ( - - )} - - {!loading && initialRecord && isViewAsJson && ( -
- -
+ {!loading && initialRecord && ( + <> + {/* Create and Edit do not show child records */} + {!isViewAsJson && action !== 'view' && ( + + )} + {!isViewAsJson && action === 'view' && ( + { + value === 'children' && trackEvent(ANALYTICS_KEYS.record_modal_view_children); + }} + tabs={tabs} + /> + )} + {isViewAsJson && ( +
+ +
+ )} + )}
diff --git a/apps/jetstream/src/app/components/core/app-routes.ts b/apps/jetstream/src/app/components/core/app-routes.ts index 47f291bbe..a812f56cb 100644 --- a/apps/jetstream/src/app/components/core/app-routes.ts +++ b/apps/jetstream/src/app/components/core/app-routes.ts @@ -78,7 +78,7 @@ export const APP_ROUTES: RouteMap = { CREATE_FIELDS: { ROUTE: '/create-fields', DOCS: 'https://docs.getjetstream.app/deploy-fields', - TITLE: 'Create Fields', + TITLE: 'Create Object and Fields', DESCRIPTION: 'Create and update fields in bulk', }, FORMULA_EVALUATOR: { diff --git a/apps/jetstream/src/app/components/core/jobs/job-utils.ts b/apps/jetstream/src/app/components/core/jobs/job-utils.ts index e82f3b6e5..e880ecbd9 100644 --- a/apps/jetstream/src/app/components/core/jobs/job-utils.ts +++ b/apps/jetstream/src/app/components/core/jobs/job-utils.ts @@ -1,12 +1,17 @@ -import { AsyncJob, RecordResult, ErrorResult } from '@jetstream/types'; -import { unparse } from 'papaparse'; -import { saveFile } from '@jetstream/shared/ui-utils'; +import { logger } from '@jetstream/shared/client-logger'; import { MIME_TYPES } from '@jetstream/shared/constants'; +import { saveFile } from '@jetstream/shared/ui-utils'; +import { AsyncJob, ErrorResult, RecordResult } from '@jetstream/types'; +import { unparse } from 'papaparse'; export function downloadJob(job: AsyncJob) { switch (job.type) { case 'BulkDelete': { const results = job.results as RecordResult[]; + if (!Array.isArray(results)) { + logger.warn('Job results are not an array, ignoring results', results); + return; + } const data = results.map((result) => { if (result.success) { @@ -17,7 +22,7 @@ export function downloadJob(job: AsyncJob) { fields: '', }; } else { - const errors = (result as ErrorResult).errors; + const errors = (result as ErrorResult)?.errors || []; return { id: '', success: false, diff --git a/apps/jetstream/src/app/components/core/record-lookup/RecordLookupPopover.tsx b/apps/jetstream/src/app/components/core/record-lookup/RecordSearchPopover.tsx similarity index 97% rename from apps/jetstream/src/app/components/core/record-lookup/RecordLookupPopover.tsx rename to apps/jetstream/src/app/components/core/record-lookup/RecordSearchPopover.tsx index b093272aa..a09a03050 100644 --- a/apps/jetstream/src/app/components/core/record-lookup/RecordLookupPopover.tsx +++ b/apps/jetstream/src/app/components/core/record-lookup/RecordSearchPopover.tsx @@ -3,7 +3,7 @@ import { INDEXED_DB } from '@jetstream/shared/constants'; import { describeGlobal } from '@jetstream/shared/data'; import { convertId15To18, hasModifierKey, isKKey, useGlobalEventHandler } from '@jetstream/shared/ui-utils'; import { CloneEditView, MapOf, SalesforceOrgUi } from '@jetstream/types'; -import { getModifierKey, Grid, Icon, Input, KeyboardShortcut, Popover, PopoverRef, ScopedNotification, Spinner } from '@jetstream/ui'; +import { Grid, Icon, Input, KeyboardShortcut, Popover, PopoverRef, ScopedNotification, Spinner, getModifierKey } from '@jetstream/ui'; import localforage from 'localforage'; import uniqBy from 'lodash/uniqBy'; import { Fragment, FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'; @@ -20,7 +20,7 @@ interface RecentRecord { const NUM_HISTORY_ITEMS = 10; -export const RecordLookupPopover: FunctionComponent = () => { +export const RecordSearchPopover: FunctionComponent = () => { const popoverRef = useRef(null); const retainRecordId = useRef(false); const [{ defaultApiVersion }] = useRecoilState(applicationCookieState); @@ -265,4 +265,4 @@ export const RecordLookupPopover: FunctionComponent = () => { ); }; -export default RecordLookupPopover; +export default RecordSearchPopover; diff --git a/apps/jetstream/src/app/components/create-object-and-fields/CreateFields.tsx b/apps/jetstream/src/app/components/create-object-and-fields/CreateFields.tsx index 9c2c6a6e9..468dffef8 100644 --- a/apps/jetstream/src/app/components/create-object-and-fields/CreateFields.tsx +++ b/apps/jetstream/src/app/components/create-object-and-fields/CreateFields.tsx @@ -196,9 +196,11 @@ export const CreateFields: FunctionComponent = () => { {rows.map((row, i) => ( 1} selectedOrg={selectedOrg} + selectedSObjects={selectedSObjects} values={row} allValid={row._allValid} onChange={(field, value) => changeRow(row._key, field, value)} diff --git a/apps/jetstream/src/app/components/create-object-and-fields/CreateFieldsDeployModalRow.tsx b/apps/jetstream/src/app/components/create-object-and-fields/CreateFieldsDeployModalRow.tsx index fe33c44e1..f51c48a36 100644 --- a/apps/jetstream/src/app/components/create-object-and-fields/CreateFieldsDeployModalRow.tsx +++ b/apps/jetstream/src/app/components/create-object-and-fields/CreateFieldsDeployModalRow.tsx @@ -49,6 +49,7 @@ export const CreateFieldsDeployModalRow: FunctionComponent diff --git a/apps/jetstream/src/app/components/create-object-and-fields/CreateFieldsFormulaEditor.tsx b/apps/jetstream/src/app/components/create-object-and-fields/CreateFieldsFormulaEditor.tsx new file mode 100644 index 000000000..d491b68fc --- /dev/null +++ b/apps/jetstream/src/app/components/create-object-and-fields/CreateFieldsFormulaEditor.tsx @@ -0,0 +1,385 @@ +import { css } from '@emotion/react'; +import { logger } from '@jetstream/shared/client-logger'; +import { ANALYTICS_KEYS } from '@jetstream/shared/constants'; +import { useNonInitialEffect } from '@jetstream/shared/ui-utils'; +import { SplitWrapper as Split } from '@jetstream/splitjs'; +import { Maybe, SalesforceOrgUi } from '@jetstream/types'; +import { Grid, KeyboardShortcut, Modal, Spinner, Tabs, Textarea } from '@jetstream/ui'; +import Editor, { OnMount, useMonaco } from '@monaco-editor/react'; +import * as formulon from 'formulon'; +import { Field, FieldType } from 'jsforce'; +import type { editor } from 'monaco-editor'; +import { forwardRef, useCallback, useEffect, useRef, useState } from 'react'; +import { useAmplitude } from '../core/analytics'; +import { registerCompletions } from '../formula-evaluator/formula-evaluator.editor-utils'; +import { NullNumberBehavior } from '../formula-evaluator/formula-evaluator.state'; +import { + FieldDefinition, + FieldValue, + FieldValueState, + FieldValues, + ManualFormulaRecord, + SalesforceFieldType, +} from '../shared/create-fields/create-fields-types'; +import FormulaEvaluatorRecordSearch from '../shared/formula-evaluator/FormulaEvaluatorRecordSearch'; +import FormulaEvaluatorResults from '../shared/formula-evaluator/FormulaEvaluatorResults'; +import FormulaEvaluatorUserSearch from '../shared/formula-evaluator/FormulaEvaluatorUserSearch'; +import { getFormulaData } from '../shared/formula-evaluator/formula-evaluator.utils'; +import CreateFieldsFormulaEditorManualField from './CreateFieldsFormulaEditorManualField'; + +export interface CreateFieldsFormulaEditorProps { + id: string; + selectedOrg: SalesforceOrgUi; + selectedSObjects: string[]; + field: FieldDefinition; + disabled: Maybe; + allValues: FieldValues; + rows: FieldValues[]; + valueState: FieldValueState; + onChange: (value: FieldValue) => void; + onBlur: () => void; +} + +export const CreateFieldsFormulaEditor = forwardRef( + ({ id, selectedOrg, selectedSObjects = [], allValues, field, valueState, disabled = false, rows, onChange, onBlur }, ref) => { + const { value, touched, errorMessage } = valueState; + const isMounted = useRef(true); + const editorRef = useRef(); + const { trackEvent } = useAmplitude(); + + const [isOpen, setIsOpen] = useState(false); + const [loading, setLoading] = useState(false); + + const [fieldErrorMessage, setFieldErrorMessage] = useState(null); + const [formulaErrorMessage, setFormulaErrorMessage] = useState(null); + const [formulaValue, setFormulaValue] = useState((value as string) || ''); + const [additionalFieldMetadata, setAdditionalFieldMetadata] = useState[]>([]); + + const [testMethod, setTestMethod] = useState<'RECORD' | 'MANUAL'>('RECORD'); + const [formulaFields, setFormulaFields] = useState([]); + const [formulaFieldValues, setFormulaFieldValues] = useState({}); + + const [results, setResults] = useState<{ formulaFields: formulon.FormulaData; parsedFormula: formulon.FormulaResult } | null>(null); + + const [selectedUserId, setSelectedUserId] = useState(''); + const [recordId, setRecordId] = useState(''); + + const monaco = useMonaco(); + + useEffect(() => { + isMounted.current = true; + return () => { + isMounted.current = false; + }; + }, []); + + useEffect(() => { + monaco && registerCompletions(monaco, selectedOrg, selectedSObjects[0], additionalFieldMetadata); + }, [monaco, selectedOrg, additionalFieldMetadata, selectedSObjects]); + + /** Reset Results */ + useEffect(() => { + setResults(null); + }, [testMethod]); + + useEffect(() => { + setFormulaFields((prevValue) => { + try { + const fields = formulon.extract(formulaValue || ''); + return fields; + } catch (ex) { + return prevValue; + } + }); + }, [formulaValue]); + + useEffect(() => { + if (isOpen) { + setTestMethod('RECORD'); + setFormulaFields([]); + setFormulaFieldValues({}); + setResults(null); + setFormulaValue((value as string) || ''); + setAdditionalFieldMetadata( + rows + .filter((row) => row.fullName.value && allValues.fullName.value !== row.fullName.value) + .map((row): Partial => { + return { + name: (row.fullName.value as string) || '', + label: `${(row.label.value as string) || ''} (NEW FIELD)`, + type: row.type.value as FieldType, + relationshipName: (row.relationshipName.value as string) || null, + referenceTo: row.referenceTo.value ? [row.referenceTo.value as string] : [], + }; + }) + ); + } + }, [allValues.fullName.value, isOpen, rows, value]); + + const handleTestFormula = useCallback( + async (value: string) => { + try { + setLoading(true); + setFieldErrorMessage(null); + setFormulaErrorMessage(null); + setResults(null); + + let formulaFieldResults: formulon.FormulaData = {}; + if (formulaFields.length) { + let payload: Parameters[0] = { + fields: formulaFields, + recordId, + selectedOrg, + selectedUserId, + sobjectName: selectedSObjects[0] || '', + numberNullBehavior: (allValues.formulaTreatBlanksAs.value as NullNumberBehavior) || 'BLANK', + }; + + if (testMethod === 'MANUAL' || !recordId) { + // ensure all fields are included in record + const record = { + ...formulaFieldValues, + }; + formulaFields.forEach((field) => { + if (!record[field]) { + record[field] = { + type: 'string', + value: null, + }; + } + }); + payload = { + fields: formulaFields, + type: 'PROVIDED_RECORD', + record, + selectedOrg, + selectedUserId, + sobjectName: selectedSObjects[0] || '', + numberNullBehavior: (allValues.formulaTreatBlanksAs.value as NullNumberBehavior) || 'BLANK', + }; + } + + const response = await getFormulaData(payload); + if (response.type === 'error') { + setFieldErrorMessage(response.message); + return; + } + formulaFieldResults = response.formulaFields; + } + const parsedFormula = formulon.parse(value, formulaFieldResults); + logger.log('results', parsedFormula); + setResults({ + formulaFields: formulaFieldResults, + parsedFormula, + }); + trackEvent(ANALYTICS_KEYS.sobj_create_field_formula_execute, { success: true, fieldCount: formulaFields.length, testMethod }); + } catch (ex) { + logger.warn(ex); + setFormulaErrorMessage(ex.message); + trackEvent(ANALYTICS_KEYS.sobj_create_field_formula_execute, { success: false, message: ex.message, stack: ex.stack }); + } finally { + setLoading(false); + } + }, + [ + allValues.formulaTreatBlanksAs.value, + formulaFieldValues, + formulaFields, + recordId, + selectedOrg, + selectedSObjects, + selectedUserId, + testMethod, + trackEvent, + ] + ); + + useNonInitialEffect(() => { + if (monaco && editorRef.current) { + editorRef.current.addAction({ + id: 'modifier-enter', + label: 'Submit', + keybindings: [monaco?.KeyMod.CtrlCmd | monaco?.KeyCode.Enter], + run: (currEditor) => { + handleTestFormula(currEditor.getValue()); + }, + }); + } + }, [handleTestFormula, monaco, selectedOrg]); + + function handleEditorChange(value, event) { + setFormulaValue(value); + } + + const handleApexEditorMount: OnMount = (currEditor, monaco) => { + editorRef.current = currEditor; + // this did not run on initial render if used in useEffect + editorRef.current.addAction({ + id: 'modifier-enter', + label: 'Submit', + keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter], + run: (currEditor) => { + handleTestFormula(currEditor.getValue()); + }, + }); + }; + + function handleClose() { + onChange(formulaValue); + setIsOpen(false); + } + + return ( +
+ + + {isOpen && ( + + + + } + onClose={handleClose} + > +
+ {loading && } + + + + +
+ +
+ +

+ Test Formula +

+ setTestMethod(value)} + initialActiveId={testMethod} + tabs={[ + { + id: 'RECORD', + title: 'Use Record', + content: ( + + ), + }, + { + id: 'MANUAL', + title: 'Use Manual Values', + content: ( + + {!formulaFields.length &&

There are no record fields in your formula.

} + {formulaFields.map((field) => ( + { + setFormulaFieldValues((prevValue) => ({ + ...prevValue, + [field]: { + type, + value, + }, + })); + }} + /> + ))} +
+ ), + }, + ]} + >
+
+ + + +
+
+
+
+ )} +
+ ); + } +); + +export default CreateFieldsFormulaEditor; diff --git a/apps/jetstream/src/app/components/create-object-and-fields/CreateFieldsFormulaEditorManualField.tsx b/apps/jetstream/src/app/components/create-object-and-fields/CreateFieldsFormulaEditorManualField.tsx new file mode 100644 index 000000000..2564a1616 --- /dev/null +++ b/apps/jetstream/src/app/components/create-object-and-fields/CreateFieldsFormulaEditorManualField.tsx @@ -0,0 +1,162 @@ +import { ListItem } from '@jetstream/types'; +import { Checkbox, ComboboxWithItems, DatePicker, DateTime, Grid, Input, TimePicker } from '@jetstream/ui'; +import formatISO from 'date-fns/formatISO'; +import isValid from 'date-fns/isValid'; +import parseISO from 'date-fns/parseISO'; +import isDate from 'lodash/isDate'; +import { forwardRef } from 'react'; +import { FieldValue, ManualFormulaFieldType } from '../shared/create-fields/create-fields-types'; + +const FieldTypeItems: ListItem[] = [ + { id: 'string', label: 'Text', value: 'string' }, + { id: 'double', label: 'Number', value: 'double' }, + { id: 'boolean', label: 'Checkbox', value: 'boolean' }, + { id: 'date', label: 'Date', value: 'date' }, + { id: 'datetime', label: 'Datetime', value: 'datetime' }, + { id: 'time', label: 'Time', value: 'time' }, +]; + +function getNumericValue(value: FieldValue): string { + if (typeof value === 'number') { + return String(value); + } + if (value === '-') { + return value; + } + if (typeof value === 'string') { + const currValue = parseFloat(value); + return isNaN(currValue) ? '' : String(currValue); + } + return ''; +} + +function getDateValue(value: FieldValue): Date { + if (typeof value === 'string') { + const date = parseISO(value); + return isValid(date) ? date : new Date(); + } + return new Date(); +} + +export interface CreateFieldsFormulaEditorManualFieldProps { + field: string; + fieldType: ManualFormulaFieldType; + fieldValue: FieldValue; + disabled?: boolean; + onChange: (type: ManualFormulaFieldType, value: FieldValue | null) => void; +} + +export const CreateFieldsFormulaEditorManualField = forwardRef( + ({ field, fieldType, fieldValue, disabled, onChange }, ref) => { + function handleTypeChange(item: ListItem) { + const value = fieldValue; + const newType = item.value; + if (newType === 'string') { + onChange(newType, String(value)); + } else if (newType === 'double') { + onChange(newType, getNumericValue(value)); + } else if (newType === 'boolean') { + onChange(newType, !!value); + } else if (newType === 'date') { + onChange(newType, formatISO(getDateValue(value), { representation: 'date' })); + } else if (newType === 'datetime') { + onChange(newType, formatISO(getDateValue(value))); + } else if (newType === 'time') { + onChange(newType, String(value)); + } else { + throw new Error(`Unknown type: ${newType}`); + } + } + + return ( + <> +

{field}

+ + + {fieldType === 'string' && ( + + onChange(fieldType, event.target.value)} + disabled={disabled} + /> + + )} + {fieldType === 'double' && ( + + onChange(fieldType, event.target.value)} + disabled={disabled} + /> + + )} + {fieldType === 'boolean' && ( + onChange(fieldType, value)} + /> + )} + {fieldType === 'date' && ( + onChange(fieldType, isDate(value) ? formatISO(value, { representation: 'date' }) : null)} + /> + )} + {fieldType === 'datetime' && ( + onChange(fieldType, isDate(value) ? formatISO(value) : null)} + /> + )} + {fieldType === 'time' && ( + onChange(fieldType, value)} + /> + )} + + + ); + } +); + +export default CreateFieldsFormulaEditorManualField; diff --git a/apps/jetstream/src/app/components/create-object-and-fields/CreateFieldsImportExport.tsx b/apps/jetstream/src/app/components/create-object-and-fields/CreateFieldsImportExport.tsx index 15a15173a..0d69c4c0e 100644 --- a/apps/jetstream/src/app/components/create-object-and-fields/CreateFieldsImportExport.tsx +++ b/apps/jetstream/src/app/components/create-object-and-fields/CreateFieldsImportExport.tsx @@ -1,6 +1,6 @@ import { ANALYTICS_KEYS, INPUT_ACCEPT_FILETYPES } from '@jetstream/shared/constants'; import { parseFile } from '@jetstream/shared/ui-utils'; -import { ensureBoolean, REGEX } from '@jetstream/shared/utils'; +import { REGEX, ensureBoolean } from '@jetstream/shared/utils'; import { InputReadFileContent, SalesforceOrgUi } from '@jetstream/types'; import { ButtonGroupContainer, @@ -8,15 +8,15 @@ import { FileDownloadModal, FileSelector, Icon, - onParsedMultipleWorkbooks, Popover, PopoverRef, + fireToast, + onParsedMultipleWorkbooks, } from '@jetstream/ui'; import { Fragment, FunctionComponent, useRef, useState } from 'react'; import { useRecoilState } from 'recoil'; import { applicationCookieState } from '../../app-state'; import { useAmplitude } from '../core/analytics'; -import { fireToast } from '../core/AppToast'; import * as fromJetstreamEvents from '../core/jetstream-events'; import { FieldValues } from '../shared/create-fields/create-fields-types'; import { diff --git a/apps/jetstream/src/app/components/create-object-and-fields/CreateFieldsRow.tsx b/apps/jetstream/src/app/components/create-object-and-fields/CreateFieldsRow.tsx index 85d97f07f..1094d2f02 100644 --- a/apps/jetstream/src/app/components/create-object-and-fields/CreateFieldsRow.tsx +++ b/apps/jetstream/src/app/components/create-object-and-fields/CreateFieldsRow.tsx @@ -1,6 +1,6 @@ import { SalesforceOrgUi } from '@jetstream/types'; import { Grid, Icon, Tooltip } from '@jetstream/ui'; -import React, { FunctionComponent } from 'react'; +import { FunctionComponent } from 'react'; import { FieldDefinitionType, FieldValue, FieldValues, SalesforceFieldType } from '../shared/create-fields/create-fields-types'; import { baseFields, @@ -17,8 +17,10 @@ export interface CreateFieldsRowProps { rowIdx: number; enableDelete?: boolean; selectedOrg: SalesforceOrgUi; + selectedSObjects: string[]; values: FieldValues; allValid: boolean; + rows: FieldValues[]; onChange: (field: FieldDefinitionType, value: FieldValue) => void; onClone: () => void; onDelete: () => void; @@ -30,8 +32,10 @@ export const CreateFieldsRow: FunctionComponent = ({ rowIdx, enableDelete, selectedOrg, + selectedSObjects, values, allValid, + rows, onChange, onClone, onDelete, @@ -49,6 +53,8 @@ export const CreateFieldsRow: FunctionComponent = ({ = ({ = ({ = ({ void; onBlur: () => void; } -export const CreateFieldsRowField = forwardRef( - ({ selectedOrg, id, field, allValues, valueState, disabled: _disabled, onChange, onBlur }, ref) => { +export const CreateFieldsRowField = forwardRef( + ({ selectedOrg, id, selectedSObjects, field, allValues, valueState, disabled: _disabled, rows, onChange, onBlur }, ref) => { const { value, touched, errorMessage } = valueState; const disabled = _disabled || field?.disabled?.(allValues); const [values, setValues] = useState([]); @@ -196,6 +199,29 @@ export const CreateFieldsRowField = forwardRef(
); + case 'textarea-with-formula': + return ( +
+ {loadingValues && } + +
+ ); case 'text': default: return ( diff --git a/apps/jetstream/src/app/components/create-object-and-fields/CreateFieldsRowPicklistOption.tsx b/apps/jetstream/src/app/components/create-object-and-fields/CreateFieldsRowPicklistOption.tsx index e416ec740..8a7785f5e 100644 --- a/apps/jetstream/src/app/components/create-object-and-fields/CreateFieldsRowPicklistOption.tsx +++ b/apps/jetstream/src/app/components/create-object-and-fields/CreateFieldsRowPicklistOption.tsx @@ -1,15 +1,17 @@ import { SalesforceOrgUi } from '@jetstream/types'; import { Checkbox, Grid } from '@jetstream/ui'; -import React, { FunctionComponent, useRef } from 'react'; -import { FieldDefinitions, FieldDefinitionType, FieldValue, FieldValues } from '../shared/create-fields/create-fields-types'; +import { FunctionComponent, useRef } from 'react'; +import { FieldDefinitionType, FieldDefinitions, FieldValue, FieldValues } from '../shared/create-fields/create-fields-types'; import CreateFieldsRowField from './CreateFieldsRowField'; import CreateNewGlobalPicklistModal from './CreateNewGlobalPicklistModal'; export interface CreateFieldsRowPicklistOptionProps { rowIdx: number; selectedOrg: SalesforceOrgUi; + selectedSObjects: string[]; values: FieldValues; fieldDefinitions: FieldDefinitions; + rows: FieldValues[]; disabled: boolean; onChangePicklistOption: (value: boolean) => void; onChange: (field: FieldDefinitionType, value: FieldValue) => void; @@ -19,8 +21,10 @@ export interface CreateFieldsRowPicklistOptionProps { export const CreateFieldsRowPicklistOption: FunctionComponent = ({ rowIdx, selectedOrg, - values, + selectedSObjects, fieldDefinitions, + values, + rows, disabled, onChange, onChangePicklistOption, @@ -48,8 +52,10 @@ export const CreateFieldsRowPicklistOption: FunctionComponent = () => { + const sobjectListRef = useRef(); const selectedOrg = useRecoilValue(selectedOrgState); const [profiles, setProfiles] = useRecoilState(fromCreateFieldsState.profilesState); @@ -68,6 +71,14 @@ export const CreateFieldsSelection: FunctionComponent + { + createdNewObject && sobjectListRef.current?.refresh(); + }} + /> {hasSelectionsMade && ( Continue @@ -110,6 +121,7 @@ export const CreateFieldsSelection: FunctionComponent
void; +} + +export const CreateNewObject: FunctionComponent = ({ + selectedOrg, + initialSelectedProfiles = [], + initialSelectedPermissionSets = [], + onClose, +}) => { + const setSelectedProfiles = useSetRecoilState(fromCreateObjectState.selectedProfilesState); + const setSelectedPermissionSets = useSetRecoilState(fromCreateObjectState.selectedPermissionSetsState); + + const resetLabelState = useResetRecoilState(fromCreateObjectState.labelState); + const resetPluralLabelState = useResetRecoilState(fromCreateObjectState.pluralLabelState); + const resetStartsWithState = useResetRecoilState(fromCreateObjectState.startsWithState); + const resetApiNameState = useResetRecoilState(fromCreateObjectState.apiNameState); + const resetDescriptionState = useResetRecoilState(fromCreateObjectState.descriptionState); + const resetRecordNameState = useResetRecoilState(fromCreateObjectState.recordNameState); + const resetDataTypeState = useResetRecoilState(fromCreateObjectState.dataTypeState); + const resetDisplayFormatState = useResetRecoilState(fromCreateObjectState.displayFormatState); + const resetStartingNumberState = useResetRecoilState(fromCreateObjectState.startingNumberState); + const resetAllowReportsState = useResetRecoilState(fromCreateObjectState.allowReportsState); + const resetAllowActivitiesState = useResetRecoilState(fromCreateObjectState.allowActivitiesState); + const resetTrackFieldHistoryState = useResetRecoilState(fromCreateObjectState.trackFieldHistoryState); + const resetAllowInChatterGroupsState = useResetRecoilState(fromCreateObjectState.allowInChatterGroupsState); + const resetAllowSharingBulkStreamingState = useResetRecoilState(fromCreateObjectState.allowSharingBulkStreamingState); + const resetAllowSearchState = useResetRecoilState(fromCreateObjectState.allowSearchState); + const resetCreateTabState = useResetRecoilState(fromCreateObjectState.createTabState); + const resetSelectedTabIconState = useResetRecoilState(fromCreateObjectState.selectedTabIconState); + const resetProfilesState = useResetRecoilState(fromCreateObjectState.profilesState); + const resetPermissionSetsState = useResetRecoilState(fromCreateObjectState.permissionSetsState); + const resetSelectedProfilesState = useResetRecoilState(fromCreateObjectState.selectedProfilesState); + const resetSelectedPermissionSetsState = useResetRecoilState(fromCreateObjectState.selectedPermissionSetsState); + const resetObjectPermissionsState = useResetRecoilState(fromCreateObjectState.objectPermissionsState); + + const [modalOpen, setModalOpen] = useState(false); + + const resetAll = () => { + resetLabelState(); + resetPluralLabelState(); + resetStartsWithState(); + resetApiNameState(); + resetDescriptionState(); + resetRecordNameState(); + resetDataTypeState(); + resetDisplayFormatState(); + resetStartingNumberState(); + resetAllowReportsState(); + resetAllowActivitiesState(); + resetTrackFieldHistoryState(); + resetAllowInChatterGroupsState(); + resetAllowSharingBulkStreamingState(); + resetAllowSearchState(); + resetCreateTabState(); + resetSelectedTabIconState(); + resetProfilesState(); + resetPermissionSetsState(); + resetSelectedProfilesState(); + resetSelectedPermissionSetsState(); + resetObjectPermissionsState(); + }; + + const handleClose = (createdNewObject?: boolean) => { + setModalOpen(false); + onClose(createdNewObject); + resetAll(); + }; + + return ( + + + + {modalOpen && } + + ); +}; + +export default CreateNewObject; diff --git a/apps/jetstream/src/app/components/create-object-and-fields/create-new-object/CreateNewObjectForm.tsx b/apps/jetstream/src/app/components/create-object-and-fields/create-new-object/CreateNewObjectForm.tsx new file mode 100644 index 000000000..90ac8c828 --- /dev/null +++ b/apps/jetstream/src/app/components/create-object-and-fields/create-new-object/CreateNewObjectForm.tsx @@ -0,0 +1,272 @@ +import { Checkbox, ComboboxWithItems, Grid, GridCol, Input, RadioButton, RadioGroup, TabIconList, Textarea } from '@jetstream/ui'; +import { FunctionComponent, useEffect } from 'react'; +import { useRecoilState } from 'recoil'; +import * as fromCreateObjectState from './create-object-state'; +import { generateApiNameFromLabel } from './create-object-utils'; + +export interface CreateNewObjectFormProps { + loading: boolean; +} + +export const CreateNewObjectForm: FunctionComponent = ({ loading }) => { + const [label, setLabel] = useRecoilState(fromCreateObjectState.labelState); + const [pluralLabel, setPluralLabel] = useRecoilState(fromCreateObjectState.pluralLabelState); + const [startsWith, setStartsWith] = useRecoilState(fromCreateObjectState.startsWithState); + const [apiName, setApiName] = useRecoilState(fromCreateObjectState.apiNameState); + const [description, setDescription] = useRecoilState(fromCreateObjectState.descriptionState); + const [recordName, setRecordName] = useRecoilState(fromCreateObjectState.recordNameState); + const [dataType, setDataType] = useRecoilState(fromCreateObjectState.dataTypeState); + const [displayFormat, setDisplayFormat] = useRecoilState(fromCreateObjectState.displayFormatState); + const [startingNumber, setStartingNumber] = useRecoilState(fromCreateObjectState.startingNumberState); + const [allowReports, setAllowReports] = useRecoilState(fromCreateObjectState.allowReportsState); + const [allowActivities, setAllowActivities] = useRecoilState(fromCreateObjectState.allowActivitiesState); + const [trackFieldHistory, setTrackFieldHistory] = useRecoilState(fromCreateObjectState.trackFieldHistoryState); + const [allowInChatterGroups, setAllowInChatterGroups] = useRecoilState(fromCreateObjectState.allowInChatterGroupsState); + const [allowSharingBulkStreaming, setAllowSharingBulkStreaming] = useRecoilState(fromCreateObjectState.allowSharingBulkStreamingState); + const [allowSearch, setAllowSearch] = useRecoilState(fromCreateObjectState.allowSearchState); + + const [createTab, setCreateTab] = useRecoilState(fromCreateObjectState.createTabState); + const [selectedTabIcon, setSelectedTabIcon] = useRecoilState(fromCreateObjectState.selectedTabIconState); + + useEffect(() => { + setApiName(generateApiNameFromLabel(label)); + setPluralLabel(label); + setRecordName(label ? `${label} Name` : ''); + }, [label, setApiName, setPluralLabel, setRecordName]); + + function handleSelectAllCheckboxes() { + setAllowReports(true); + setAllowActivities(true); + setTrackFieldHistory(true); + setAllowInChatterGroups(true); + setAllowSharingBulkStreaming(true); + setAllowSearch(true); + } + + return ( + + + + setLabel(event.target.value)} + disabled={loading} + maxLength={40} + required + /> + + + + + setStartsWith(item.value as 'Consonant' | 'Vowel' | 'Special')} + /> + + + + + setPluralLabel(event.target.value)} + disabled={loading} + maxLength={40} + required + /> + + + + + + setApiName(event.target.value)} + disabled={loading} + maxLength={40} + required + /> + + + + + + + + + + setRecordName(event.target.value)} + disabled={loading} + maxLength={80} + required + /> + + + + + + setDataType(value as 'Text' | 'AutoNumber')} + disabled={loading} + /> + setDataType(value as 'Text' | 'AutoNumber')} + disabled={loading} + /> + + + {dataType === 'AutoNumber' && ( + + + +
  • + {'{0}'} Required Sequence number. One or more zeros enclosed in curly braces represent the sequence number itself. The + number of zeros in the curly braces dictates the minimum number of digits that will be displayed. If the actual number + has fewer digits than this, it will be padded with leading zeros. Maximum is 10 digits. +
  • +
  • + {'{YY} {YYYY}'} Optional Year. 2 or 4 "Y" characters enclosed in curly braces represent the year of the record + creation date. You can display 2 digits (for example, "04") or all 4 digits (for example, "2004") of the year. +
  • +
  • + {'{MM}'} Optional Month. 2 "M" characters enclosed in curly braces represent the numeric month (for example, "01" for + January, "02" for February) of the record creation date. +
  • +
  • + {'{DD}'} Optional Day. 2 "D" characters enclosed in curly braces represent the numeric day of the month (for example, + "01" to "31" are valid days in January) of the record creation date. +
  • + + } + > + setDisplayFormat(event.target.value)} + disabled={loading} + maxLength={50} + required + /> + +
    + + + setStartingNumber(event.target.value.replace(/\D/g, ''))} + disabled={loading} + maxLength={50} + required + /> + + +
    + )} +
    + + +
    +
    + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + {createTab && ( + + + + )} +
    + ); +}; + +export default CreateNewObjectFormProps; diff --git a/apps/jetstream/src/app/components/create-object-and-fields/create-new-object/CreateNewObjectModal.tsx b/apps/jetstream/src/app/components/create-object-and-fields/create-new-object/CreateNewObjectModal.tsx new file mode 100644 index 000000000..aade52e47 --- /dev/null +++ b/apps/jetstream/src/app/components/create-object-and-fields/create-new-object/CreateNewObjectModal.tsx @@ -0,0 +1,237 @@ +import { css } from '@emotion/react'; +import { ANALYTICS_KEYS } from '@jetstream/shared/constants'; +import { formatNumber } from '@jetstream/shared/ui-utils'; +import { SalesforceOrgUi } from '@jetstream/types'; +import { EmptyState, Grid, GridCol, Icon, Modal, PreviewIllustration, SalesforceLogin, Spinner, Tabs, TabsRef } from '@jetstream/ui'; +import { FunctionComponent, useRef } from 'react'; +import { useRecoilState, useRecoilValue } from 'recoil'; +import { applicationCookieState, selectSkipFrontdoorAuth } from '../../../app-state'; +import ConfirmPageChange from '../../core/ConfirmPageChange'; +import { useAmplitude } from '../../core/analytics'; +import DeployMetadataProgressSummary from '../../deploy/utils/DeployMetadataProgressSummary'; +import DeployMetadataResultsTables from '../../deploy/utils/DeployMetadataResultsTables'; +import { CreateNewObjectForm } from './CreateNewObjectForm'; +import CreateNewObjectPermissions from './CreateNewObjectPermissions'; +import CreateNewObjectPermissionsResult from './CreateNewObjectPermissionsResult'; +import * as fromCreateObjectState from './create-object-state'; +import useCreateObject, { getFriendlyStatus } from './useCreateObject'; + +export interface CreateNewObjectModalProps { + selectedOrg: SalesforceOrgUi; + initialSelectedProfiles?: string[]; + initialSelectedPermissionSets?: string[]; + onClose: (createdNewObject?: boolean) => void; +} + +export const CreateNewObjectModal: FunctionComponent = ({ selectedOrg, onClose }) => { + const { trackEvent } = useAmplitude(); + const [{ defaultApiVersion, serverUrl }] = useRecoilState(applicationCookieState); + const skipFrontDoorAuth = useRecoilValue(selectSkipFrontdoorAuth); + + const modalRef = useRef(); + const modalBodyRef = useRef(null); + const tabsRef = useRef(); + + const apiNameWithoutNamespace = useRecoilValue(fromCreateObjectState.apiNameState); + const createTab = useRecoilValue(fromCreateObjectState.createTabState); + const selectedTabIcon = useRecoilValue(fromCreateObjectState.selectedTabIconState); + const objectPermissions = useRecoilValue(fromCreateObjectState.objectPermissionsState); + const selectedPermissionSets = useRecoilValue(fromCreateObjectState.selectedPermissionSetsState); + const selectedProfiles = useRecoilValue(fromCreateObjectState.selectedProfilesState); + const payload = useRecoilValue(fromCreateObjectState.payloadSelector); + const isValid = useRecoilValue(fromCreateObjectState.isFormValid); + + const apiName = `${selectedOrg.orgNamespacePrefix ? `${selectedOrg.orgNamespacePrefix}__` : ''}${apiNameWithoutNamespace}`; + + const { results, deployMetadata, status, errorMessage, hasError, loading, permissionRecordResults } = useCreateObject({ + apiVersion: defaultApiVersion, + serverUrl, + selectedOrg, + }); + + async function handleDeploy(event: React.FormEvent) { + event.preventDefault(); + if (!isValid || !payload) { + return; + } + + trackEvent(ANALYTICS_KEYS.sobj_create_object, { + allowReports: payload.enableReports, + allowActivities: payload.enableActivities, + trackFieldHistory: payload.enableHistory, + allowInChatterGroups: payload.allowInChatterGroups, + allowSharingBulkStreaming: payload.enableBulkApi, + allowSearch: payload.enableSearch, + createTab: createTab, + selectedProfiles: selectedProfiles.length, + selectedPermissionSets: selectedPermissionSets.length, + }); + + tabsRef.current?.changeTab('results'); + await deployMetadata({ + apiName, + createTab, + tabMotif: selectedTabIcon, + payload, + objectPermissions, + permissionSets: selectedPermissionSets, + profiles: selectedProfiles, + }); + } + + function handleCloseModal() { + if (!loading) { + onClose(!!results); + } + } + + return ( + <> + + +
    + + +
    + + } + size="lg" + onClose={handleCloseModal} + > +
    +
    + + Permissions ({formatNumber(selectedProfiles.length + selectedPermissionSets.length)}) + + ), + titleText: 'Permissions', + content: , + }, + { + id: 'field', + title: ( + + Object Configuration + + ), + content: , + }, + { + id: 'results', + title: ( + + Results {getFriendlyStatus(status)} + {loading && ( +
    + +
    + )} +
    + ), + titleText: 'Results', + content: ( + + {loading && !results && } + + {status === 'NOT_STARTED' && ( + + }> + + )} + + {!results && hasError && ( + +
    + {errorMessage || 'Unknown error'} + +
    +
    + )} + + {status !== 'NOT_STARTED' && ( + +

    Object and Tab Results

    + {results?.success && ( + + View object in Salesforce setup + + )} +
    + )} + + {results && ( + <> + + + + + + + + + )} + {(permissionRecordResults || status === 'LOADING_PERMISSIONS') && ( + +

    Permission Results

    + {status === 'LOADING_PERMISSIONS' && } + {permissionRecordResults && } +
    + )} +
    + ), + }, + ]} + >
    +
    +
    +
    + + ); +}; + +export default CreateNewObjectModal; diff --git a/apps/jetstream/src/app/components/create-object-and-fields/create-new-object/CreateNewObjectPermissions.tsx b/apps/jetstream/src/app/components/create-object-and-fields/create-new-object/CreateNewObjectPermissions.tsx new file mode 100644 index 000000000..e69a6a711 --- /dev/null +++ b/apps/jetstream/src/app/components/create-object-and-fields/create-new-object/CreateNewObjectPermissions.tsx @@ -0,0 +1,132 @@ +import { useNonInitialEffect, useProfilesAndPermSets } from '@jetstream/shared/ui-utils'; +import { SalesforceOrgUi } from '@jetstream/types'; +import { Checkbox, Grid, ListWithFilterMultiSelect } from '@jetstream/ui'; +import { FunctionComponent, useState } from 'react'; +import { useRecoilState } from 'recoil'; +import * as fromCreateObjectState from './create-object-state'; +import { CreateObjectPermissions } from './create-object-types'; + +export interface CreateNewObjectPermissionsProps { + selectedOrg: SalesforceOrgUi; + loading: boolean; + portalRef?: Element; +} + +export const CreateNewObjectPermissions: FunctionComponent = ({ selectedOrg, loading, portalRef }) => { + const [profiles, setProfiles] = useRecoilState(fromCreateObjectState.profilesState); + const [selectedProfiles, setSelectedProfiles] = useRecoilState(fromCreateObjectState.selectedProfilesState); + + const [permissionSets, setPermissionSets] = useRecoilState(fromCreateObjectState.permissionSetsState); + const [selectedPermissionSets, setSelectedPermissionSets] = useRecoilState(fromCreateObjectState.selectedPermissionSetsState); + + const profilesAndPermSetsData = useProfilesAndPermSets(selectedOrg, profiles, permissionSets); + + const [objectPermissions, setObjectPermissions] = useState({ + allowCreate: true, + allowDelete: true, + allowEdit: true, + allowRead: true, + modifyAllRecords: true, + viewAllRecords: true, + }); + + useNonInitialEffect(() => { + setProfiles(profilesAndPermSetsData.profiles); + setPermissionSets(profilesAndPermSetsData.permissionSets); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [profilesAndPermSetsData.profiles, profilesAndPermSetsData.permissionSets]); + + return ( + +
    + profilesAndPermSetsData.fetchMetadata(true)} + /> +
    +
    + profilesAndPermSetsData.fetchMetadata(true)} + /> +
    +
    +

    Object Permissions

    + setObjectPermissions((priorValue) => ({ ...priorValue, allowCreate: value }))} + disabled={loading} + /> + setObjectPermissions((priorValue) => ({ ...priorValue, allowDelete: value }))} + disabled={loading} + /> + setObjectPermissions((priorValue) => ({ ...priorValue, allowEdit: value }))} + disabled={loading} + /> + setObjectPermissions((priorValue) => ({ ...priorValue, allowRead: value }))} + disabled={loading} + /> + setObjectPermissions((priorValue) => ({ ...priorValue, modifyAllRecords: value }))} + disabled={loading} + /> + setObjectPermissions((priorValue) => ({ ...priorValue, viewAllRecords: value }))} + disabled={loading} + /> +
    +
    + ); +}; + +export default CreateNewObjectPermissions; diff --git a/apps/jetstream/src/app/components/create-object-and-fields/create-new-object/CreateNewObjectPermissionsResult.tsx b/apps/jetstream/src/app/components/create-object-and-fields/create-new-object/CreateNewObjectPermissionsResult.tsx new file mode 100644 index 000000000..9432d2279 --- /dev/null +++ b/apps/jetstream/src/app/components/create-object-and-fields/create-new-object/CreateNewObjectPermissionsResult.tsx @@ -0,0 +1,51 @@ +import { Maybe } from '@jetstream/types'; +import { Icon } from '@jetstream/ui'; +import { FunctionComponent } from 'react'; + +export interface CreateNewObjectPermissionsResultProps { + recordResults: { + skipped: number; + success: number; + failed: number; + errors: Maybe[]; + }; +} + +export const CreateNewObjectPermissionsResult: FunctionComponent = ({ recordResults }) => { + if (!recordResults.errors || recordResults.errors.length === 0) { + return ( +
    + + All permissions deployed successfully +
    + ); + } + + return ( +
    +
    + + There was an error deploying some permissions +
    +
      + {recordResults.errors.map((error, i) => ( +
    • {error}
    • + ))} +
    +
    + ); +}; + +export default CreateNewObjectPermissionsResult; diff --git a/apps/jetstream/src/app/components/create-object-and-fields/create-new-object/create-object-state.ts b/apps/jetstream/src/app/components/create-object-and-fields/create-new-object/create-object-state.ts new file mode 100644 index 000000000..c21c1aac3 --- /dev/null +++ b/apps/jetstream/src/app/components/create-object-and-fields/create-new-object/create-object-state.ts @@ -0,0 +1,104 @@ +import { REGEX } from '@jetstream/shared/utils'; +import { ListItem, PermissionSetNoProfileRecord, PermissionSetWithProfileRecord } from '@jetstream/types'; +import { atom, selector } from 'recoil'; +import { CreateObjectPayload, CreateObjectPermissions } from './create-object-types'; + +export const labelState = atom({ key: 'create-object.labelState', default: '' }); +export const pluralLabelState = atom({ key: 'create-object.pluralLabelState', default: '' }); +export const startsWithState = atom<'Consonant' | 'Vowel' | 'Special'>({ key: 'create-object.startsWithState', default: 'Consonant' }); +export const apiNameState = atom({ key: 'create-object.apiNameState', default: '' }); +export const descriptionState = atom({ key: 'create-object.descriptionState', default: '' }); +export const recordNameState = atom({ key: 'create-object.recordNameState', default: '' }); +export const dataTypeState = atom<'Text' | 'AutoNumber'>({ key: 'create-object.dataTypeState', default: 'Text' }); +export const displayFormatState = atom({ key: 'create-object.displayFormatState', default: '' }); +export const startingNumberState = atom({ key: 'create-object.startingNumberState', default: '1' }); +export const allowReportsState = atom({ key: 'create-object.allowReportsState', default: false }); +export const allowActivitiesState = atom({ key: 'create-object.allowActivitiesState', default: false }); +export const trackFieldHistoryState = atom({ key: 'create-object.trackFieldHistoryState', default: false }); +export const allowInChatterGroupsState = atom({ key: 'create-object.allowInChatterGroupsState', default: false }); +export const allowSharingBulkStreamingState = atom({ key: 'create-object.allowSharingBulkStreamingState', default: true }); +export const allowSearchState = atom({ key: 'create-object.allowSearchState', default: true }); + +export const createTabState = atom({ key: 'create-object.createTabState', default: true }); +export const selectedTabIconState = atom({ key: 'create-object.selectedTabIconState', default: 'Custom20: Airplane' }); + +export const profilesState = atom[] | null>({ + key: 'create-object.profilesState', + default: null, +}); +export const permissionSetsState = atom[] | null>({ + key: 'create-object.permissionSetsState', + default: null, +}); + +export const selectedProfilesState = atom({ key: 'create-object.selectedProfilesState', default: [] }); +export const selectedPermissionSetsState = atom({ key: 'create-object.selectedPermissionSetsState', default: [] }); + +export const objectPermissionsState = atom({ + key: 'create-object.objectPermissionsState', + default: { + allowCreate: true, + allowDelete: true, + allowEdit: true, + allowRead: true, + modifyAllRecords: true, + viewAllRecords: true, + }, +}); + +export const payloadSelector = selector({ + key: 'create-object.payloadSelector', + get: ({ get }) => { + const dataType = get(dataTypeState); + + const nameField: CreateObjectPayload['nameField'] = { + label: get(labelState), + trackHistory: get(trackFieldHistoryState), + type: get(dataTypeState), + }; + + if (dataType === 'AutoNumber') { + nameField.displayFormat = get(displayFormatState); + nameField.startingNumber = get(startingNumberState); + } + + const payload: CreateObjectPayload = { + allowInChatterGroups: get(allowInChatterGroupsState), + compactLayoutAssignment: 'SYSTEM', + deploymentStatus: 'Deployed', + description: get(descriptionState), + enableActivities: get(allowActivitiesState), + enableBulkApi: get(allowSharingBulkStreamingState), + enableEnhancedLookup: false, + enableFeeds: get(allowInChatterGroupsState), + enableHistory: get(trackFieldHistoryState), + enableLicensing: false, + enableReports: get(allowReportsState), + enableSearch: get(allowSearchState), + enableSharing: get(allowSharingBulkStreamingState), + enableStreamingApi: get(allowSharingBulkStreamingState), + externalSharingModel: 'Private', + label: get(labelState), + nameField, + pluralLabel: get(pluralLabelState), + recordTypeTrackHistory: false, + sharingModel: 'ReadWrite', + startsWith: get(startsWithState), + visibility: 'Public', + }; + + return payload; + }, +}); + +export const isFormValid = selector({ + key: 'create-object.isFormValid', + get: ({ get }) => { + const payload = get(payloadSelector); + let isValid = !!payload.label && !!payload.pluralLabel && !!get(apiNameState) && !!payload.nameField.label; + if (payload.nameField.type === 'AutoNumber') { + isValid = isValid && !!payload.nameField.displayFormat && REGEX.NUMERIC.test(payload.nameField.startingNumber || ''); + } + return isValid; + }, +}); diff --git a/apps/jetstream/src/app/components/create-object-and-fields/create-new-object/create-object-types.ts b/apps/jetstream/src/app/components/create-object-and-fields/create-new-object/create-object-types.ts new file mode 100644 index 000000000..deeb74a5d --- /dev/null +++ b/apps/jetstream/src/app/components/create-object-and-fields/create-new-object/create-object-types.ts @@ -0,0 +1,49 @@ +export interface CreateFieldParams { + apiName: string; + createTab: boolean; + tabMotif: string; + payload: CreateObjectPayload; + objectPermissions: CreateObjectPermissions; + permissionSets: string[]; + profiles: string[]; +} + +export interface CreateObjectPayload { + allowInChatterGroups: boolean; + compactLayoutAssignment: 'SYSTEM'; + deploymentStatus: 'Deployed'; + description?: string; + enableActivities: boolean; + enableBulkApi: boolean; + enableEnhancedLookup: false; + enableFeeds: boolean; + enableHistory: boolean; + enableLicensing: false; + enableReports: boolean; + enableSearch: boolean; + enableSharing: boolean; + enableStreamingApi: boolean; + externalSharingModel: 'Private'; + label: string; + nameField: { + label: string; + trackHistory?: boolean; + type: 'AutoNumber' | 'Text'; + displayFormat?: string; + startingNumber?: string; + }; + pluralLabel: string; + recordTypeTrackHistory: boolean; + sharingModel: 'ReadWrite'; + startsWith: 'Consonant' | 'Vowel' | 'Special'; + visibility: 'Public'; +} + +export interface CreateObjectPermissions { + allowCreate: boolean; + allowDelete: boolean; + allowEdit: boolean; + allowRead: boolean; + modifyAllRecords: boolean; + viewAllRecords: boolean; +} diff --git a/apps/jetstream/src/app/components/create-object-and-fields/create-new-object/create-object-utils.ts b/apps/jetstream/src/app/components/create-object-and-fields/create-new-object/create-object-utils.ts new file mode 100644 index 000000000..5fa5bb185 --- /dev/null +++ b/apps/jetstream/src/app/components/create-object-and-fields/create-new-object/create-object-utils.ts @@ -0,0 +1,225 @@ +import { queryAll, sobjectOperation } from '@jetstream/shared/data'; +import { REGEX, splitArrayToMaxSize } from '@jetstream/shared/utils'; +import { Maybe, ObjectPermissionRecordInsert, RecordResult, SalesforceOrgUi, TabPermissionRecordInsert } from '@jetstream/types'; +import JSZip from 'jszip'; +import partition from 'lodash/partition'; +import { composeQuery, getField } from 'soql-parser-js'; +import { CreateFieldParams, CreateObjectPayload } from './create-object-types'; + +export function generateApiNameFromLabel(value: string) { + let apiNameLabel = value; + if (apiNameLabel) { + apiNameLabel = apiNameLabel + .replace(REGEX.NOT_ALPHANUMERIC_OR_UNDERSCORE, '_') + .replace(REGEX.STARTS_WITH_UNDERSCORE, '') + .replace(REGEX.CONSECUTIVE_UNDERSCORES, '_') + .replace(REGEX.ENDS_WITH_NON_ALPHANUMERIC, ''); + if (REGEX.STARTS_WITH_NUMBER.test(apiNameLabel)) { + apiNameLabel = `X${apiNameLabel}`; + } + if (apiNameLabel.length > 40) { + apiNameLabel = apiNameLabel.substring(0, 40); + } + } + return apiNameLabel ? `${apiNameLabel}__c` : ''; +} + +export function getObjectAndTabPermissionRecords( + { apiName: apiNameWithoutNamespace, createTab, profiles, permissionSets, objectPermissions }: CreateFieldParams, + orgNamespacePrefix?: Maybe +) { + const apiName = orgNamespacePrefix ? `${orgNamespacePrefix}__${apiNameWithoutNamespace}` : apiNameWithoutNamespace; + const _objectPermissions: ObjectPermissionRecordInsert[] = []; + const tabPermissions: TabPermissionRecordInsert[] = []; + + profiles.forEach((id) => { + _objectPermissions.push({ + attributes: { type: 'ObjectPermissions' }, + ParentId: id, + PermissionsCreate: objectPermissions.allowCreate, + PermissionsDelete: objectPermissions.allowDelete, + PermissionsEdit: objectPermissions.allowEdit, + PermissionsModifyAllRecords: objectPermissions.modifyAllRecords, + PermissionsRead: objectPermissions.allowRead, + PermissionsViewAllRecords: objectPermissions.viewAllRecords, + SobjectType: apiName, + }); + if (createTab) { + tabPermissions.push({ + attributes: { type: 'PermissionSetTabSetting' }, + ParentId: id, + Name: apiName, + Visibility: 'DefaultOn', + }); + } + }); + + permissionSets.forEach((id) => { + _objectPermissions.push({ + attributes: { type: 'ObjectPermissions' }, + ParentId: id, + PermissionsCreate: objectPermissions.allowCreate, + PermissionsDelete: objectPermissions.allowDelete, + PermissionsEdit: objectPermissions.allowEdit, + PermissionsModifyAllRecords: objectPermissions.modifyAllRecords, + PermissionsRead: objectPermissions.allowRead, + PermissionsViewAllRecords: objectPermissions.viewAllRecords, + SobjectType: apiName, + }); + if (createTab) { + tabPermissions.push({ + attributes: { type: 'PermissionSetTabSetting' }, + ParentId: id, + Name: apiName, + Visibility: 'DefaultOn', + }); + } + }); + return { + tabPermissions, + objectPermissions: _objectPermissions, + }; +} + +export async function getMetadataPackage( + apiVersion: string, + { apiName, createTab, tabMotif, payload, permissionSets, profiles }: CreateFieldParams +) { + const customObjectXml = getObjectXml(payload); + + const zip = new JSZip(); + zip.file(`objects/${apiName}.object`, customObjectXml); + + if (createTab) { + const customTabXml = getTabXml(tabMotif); + zip.file(`tabs/${apiName}.tab`, customTabXml); + } + + zip.file('package.xml', getPackageXml(apiName, apiVersion, createTab, profiles, permissionSets)); + + const file = await zip.generateAsync({ type: 'arraybuffer' }); + + return file; +} + +export async function savePermissionRecords( + org: SalesforceOrgUi, + objectApiName: string, + permissionRecords: { + objectPermissions: ObjectPermissionRecordInsert[]; + tabPermissions: TabPermissionRecordInsert[]; + } +) { + const existingPermissions = await Promise.all([ + queryAll<{ ParentId: string }>( + org, + composeQuery({ + fields: [getField('ParentId')], + sObject: 'ObjectPermissions', + where: { left: { field: 'SobjectType', operator: '=', value: objectApiName, literalType: 'STRING' } }, + }) + ), + queryAll<{ ParentId: string }>( + org, + composeQuery({ + fields: [getField('ParentId')], + sObject: 'PermissionSetTabSetting', + where: { left: { field: 'Name', operator: '=', value: objectApiName, literalType: 'STRING' } }, + }) + ), + ]).then( + ([query1, query2]) => + new Set([ + ...query1.queryResults.records.map((record) => record.ParentId), + ...query2.queryResults.records.map((record) => record.ParentId), + ]) + ); + + const [skippedObjectPermissions, objectPermissions] = partition(permissionRecords.objectPermissions, (record) => + existingPermissions.has(record.ParentId) + ); + const [skippedTabPermissions, tabPermissions] = partition(permissionRecords.tabPermissions, (record) => + existingPermissions.has(record.ParentId) + ); + + const recordInsertResults: RecordResult[] = ( + await Promise.all([ + ...splitArrayToMaxSize(objectPermissions, 200).map((records) => + sobjectOperation(org, 'ObjectPermissions', 'create', { records }, { allOrNone: false }) + ), + ...splitArrayToMaxSize(tabPermissions, 200).map((records) => + sobjectOperation(org, 'PermissionSetTabSetting', 'create', { records }, { allOrNone: false }) + ), + ]) + ).flat(); + + return { + skippedObjectPermissions, + skippedTabPermissions, + recordInsertResults, + }; +} + +export function getObjectXml(payload: CreateObjectPayload) { + return ` + + ${payload.allowInChatterGroups} + ${payload.compactLayoutAssignment} + ${payload.deploymentStatus} + ${payload.description || ''} + ${payload.enableActivities} + ${payload.enableBulkApi} + ${payload.enableEnhancedLookup} + ${payload.enableFeeds} + ${payload.enableHistory} + ${payload.enableLicensing} + ${payload.enableReports} + ${payload.enableSearch} + ${payload.enableSharing} + ${payload.enableStreamingApi} + ${payload.externalSharingModel} + + + + ${payload.nameField.trackHistory} + ${payload.nameField.type} +${payload.nameField.type === 'AutoNumber' && `${payload.nameField.displayFormat}`} +${payload.nameField.type === 'AutoNumber' && `${payload.nameField.startingNumber}`} + + ${payload.pluralLabel} + ${payload.recordTypeTrackHistory} + ${payload.sharingModel} + ${payload.startsWith} + ${payload.visibility} +`; +} + +export function getTabXml(tabMotif: string) { + return ` + + true + ${tabMotif} +`; +} + +export function getPackageXml(apiName: string, apiVersion: string, createTab: boolean, profiles: string[], permissionSets: string[]) { + let xml = ` + + ${apiVersion.replace('v', '')} + + ${apiName} + CustomObject + `; + + if (createTab) { + xml += ` + + ${apiName} + CustomTab + `; + } + + xml += `\n`; + + return xml; +} diff --git a/apps/jetstream/src/app/components/create-object-and-fields/create-new-object/useCreateObject.ts b/apps/jetstream/src/app/components/create-object-and-fields/create-new-object/useCreateObject.ts new file mode 100644 index 000000000..e83c944a5 --- /dev/null +++ b/apps/jetstream/src/app/components/create-object-and-fields/create-new-object/useCreateObject.ts @@ -0,0 +1,123 @@ +import { logger } from '@jetstream/shared/client-logger'; +import { useRollbar } from '@jetstream/shared/ui-utils'; +import { Maybe, SalesforceOrgUi } from '@jetstream/types'; +import { useCallback, useState } from 'react'; +import { useDeployMetadataPackage } from '../../shared/useDeployMetadataPackage'; +import { CreateFieldParams } from './create-object-types'; +import { getMetadataPackage, getObjectAndTabPermissionRecords, savePermissionRecords } from './create-object-utils'; + +export type CreateObjectResultsStatus = 'NOT_STARTED' | 'LOADING_METADATA' | 'LOADING_PERMISSIONS' | 'SUCCESS' | 'FAILED'; + +export function getFriendlyStatus(status: CreateObjectResultsStatus) { + switch (status) { + case 'NOT_STARTED': + return ''; + case 'LOADING_METADATA': + return '(Loading Metadata)'; + case 'LOADING_PERMISSIONS': + return '(Loading Permissions)'; + case 'SUCCESS': + return '(Success)'; + case 'FAILED': + return '(Failed)'; + } +} + +interface UseCreateObjectOptions { + apiVersion: string; + serverUrl: string; + selectedOrg: SalesforceOrgUi; +} + +export default function useCreateObject({ apiVersion, serverUrl, selectedOrg }: UseCreateObjectOptions) { + const rollbar = useRollbar(); + const [loading, setLoading] = useState(false); + const [deployed, setDeployed] = useState(false); + const [status, setStatus] = useState('NOT_STARTED'); + const [permissionRecordResults, setPermissionRecordResults] = useState< + Maybe<{ + skipped: number; + success: number; + failed: number; + errors: Maybe[]; + }> + >(null); + + const { + deployMetadata: doDeployMetadata, + results, + loading: deployLoading, + lastChecked, + hasError, + errorMessage, + } = useDeployMetadataPackage(serverUrl); + + /** + * DEPLOY METADATA + */ + const deployMetadata = useCallback( + async (data: CreateFieldParams) => { + try { + const { apiName } = data; + const { orgNamespacePrefix } = selectedOrg; + logger.info('[deployMetadata]', data); + setStatus('LOADING_METADATA'); + setPermissionRecordResults(null); + const permissionRecords = getObjectAndTabPermissionRecords(data, orgNamespacePrefix); + const file = await getMetadataPackage(apiVersion, data); + + try { + const results = await doDeployMetadata(selectedOrg, file, { + rollbackOnError: false, + singlePackage: true, + allowMissingFiles: false, + }); + + if (!results?.success) { + setStatus('FAILED'); + return; + } + + if (permissionRecords?.objectPermissions.length || permissionRecords?.tabPermissions.length) { + setStatus('LOADING_PERMISSIONS'); + const permissionRecordResults = await savePermissionRecords(selectedOrg, apiName, permissionRecords); + logger.info('[permissionRecordResults]', permissionRecordResults); + setPermissionRecordResults({ + errors: permissionRecordResults.recordInsertResults + .filter((record) => !record.success) + .flatMap((record) => (!record.success ? record.errors : [])) + .map((record) => record.message), + failed: permissionRecordResults.recordInsertResults.filter((record) => !record.success).length, + skipped: permissionRecordResults.recordInsertResults.filter((record) => record.success).length, + success: permissionRecordResults.recordInsertResults.filter((record) => record.success).length, + }); + } + setStatus('SUCCESS'); + } catch (ex) { + rollbar.error('Deploy object permission records Fatal Error', { message: ex.message, stack: ex.stack }); + setStatus('FAILED'); + } finally { + setLoading(false); + setDeployed(true); + } + } catch (ex) { + rollbar.error('Deploy object Fatal Error', { message: ex.message, stack: ex.stack }); + setStatus('FAILED'); + } + }, + [apiVersion, doDeployMetadata, rollbar, selectedOrg] + ); + + return { + status, + results, + permissionRecordResults, + loading: loading || deployLoading || status === 'LOADING_METADATA' || status === 'LOADING_PERMISSIONS', + deployed, + lastChecked, + hasError, + errorMessage, + getMetadataPackage, + deployMetadata, + }; +} diff --git a/apps/jetstream/src/app/components/debug-log-viewer/DebugLogViewer.tsx b/apps/jetstream/src/app/components/debug-log-viewer/DebugLogViewer.tsx index d1dae0934..f2c352549 100644 --- a/apps/jetstream/src/app/components/debug-log-viewer/DebugLogViewer.tsx +++ b/apps/jetstream/src/app/components/debug-log-viewer/DebugLogViewer.tsx @@ -22,7 +22,7 @@ import type { editor } from 'monaco-editor'; import { Fragment, FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'; import { useRecoilState, useRecoilValue } from 'recoil'; import { filter } from 'rxjs/operators'; -import { applicationCookieState, selectedOrgState } from '../../app-state'; +import { applicationCookieState, selectSkipFrontdoorAuth, selectedOrgState } from '../../app-state'; import { RequireMetadataApiBanner } from '../core/RequireMetadataApiBanner'; import * as fromJetstreamEvents from '../core/jetstream-events'; import DebugLogViewerFilter from './DebugLogViewerFilter'; @@ -42,6 +42,7 @@ export const DebugLogViewer: FunctionComponent = () => { const logCache = useRef>({}); const logRef = useRef(); const [{ serverUrl }] = useRecoilState(applicationCookieState); + const skipFrontDoorAuth = useRecoilValue(selectSkipFrontdoorAuth); const selectedOrg = useRecoilValue(selectedOrgState); const [showLogsFromAllUsers, setShowLogsFromAllUsers] = useState(false); const [loadingLog, setLoadingLog] = useState(false); @@ -206,6 +207,7 @@ export const DebugLogViewer: FunctionComponent = () => {
    Debug Logs
    @@ -226,6 +228,7 @@ export const DebugLogViewer: FunctionComponent = () => { className="slds-m-right_x-small" serverUrl={serverUrl} org={selectedOrg} + skipFrontDoorAuth={skipFrontDoorAuth} returnUrl="/lightning/setup/ApexDebugLogs/home" omitIcon title="View debug logs in Salesforce" @@ -245,7 +248,10 @@ export const DebugLogViewer: FunctionComponent = () => {
    {lastChecked && (

    - Last Checked {formatDate(lastChecked, 'h:mm:ss')} @@ -260,6 +266,7 @@ export const DebugLogViewer: FunctionComponent = () => {

    Log Results
    } actions={ diff --git a/apps/jetstream/src/app/components/debug-log-viewer/DebugLogViewerTable.tsx b/apps/jetstream/src/app/components/debug-log-viewer/DebugLogViewerTable.tsx index da8a47f94..b48acf8b2 100644 --- a/apps/jetstream/src/app/components/debug-log-viewer/DebugLogViewerTable.tsx +++ b/apps/jetstream/src/app/components/debug-log-viewer/DebugLogViewerTable.tsx @@ -2,14 +2,14 @@ import { css } from '@emotion/react'; import { ApexLogWithViewed } from '@jetstream/types'; import { AutoFullHeightContainer, ColumnWithFilter, DataTable, Icon, setColumnFromType } from '@jetstream/ui'; import { FunctionComponent, useEffect, useRef } from 'react'; -import { CellClickArgs, FormatterProps } from 'react-data-grid'; +import { CellClickArgs, RenderCellProps } from 'react-data-grid'; -export const LogViewedRenderer: FunctionComponent> = ({ row }) => { +export const LogViewedRenderer: FunctionComponent> = ({ row }) => { if (row?.viewed) { return ( [] = [ { name: '', key: 'viewed', width: 12, - formatter: LogViewedRenderer, + renderCell: LogViewedRenderer, resizable: false, - // TODO: filter for this }, { ...setColumnFromType('LogUser.Name', 'text'), name: 'User', key: 'LogUser.Name', width: 125, + draggable: true, }, { ...setColumnFromType('Application', 'text'), name: 'Application', key: 'Application', width: 125, + draggable: true, }, { ...setColumnFromType('Operation', 'text'), name: 'Operation', key: 'Operation', width: 125, + draggable: true, }, { ...setColumnFromType('Status', 'text'), name: 'Status', key: 'Status', width: 125, + draggable: true, }, { ...setColumnFromType('LogLength', 'text'), name: 'Size', key: 'LogLength', width: 125, + draggable: true, }, { ...setColumnFromType('LastModifiedDate', 'date'), name: 'Time', key: 'LastModifiedDate', width: 202, + draggable: true, }, ]; @@ -96,7 +103,13 @@ export const DebugLogViewerTable: FunctionComponent = return ( - + ); }; diff --git a/apps/jetstream/src/app/components/debug-log-viewer/PurgeLogsModal.tsx b/apps/jetstream/src/app/components/debug-log-viewer/PurgeLogsModal.tsx index 440a422d9..3beff0c80 100644 --- a/apps/jetstream/src/app/components/debug-log-viewer/PurgeLogsModal.tsx +++ b/apps/jetstream/src/app/components/debug-log-viewer/PurgeLogsModal.tsx @@ -42,7 +42,7 @@ export const PurgeLogsModal: FunctionComponent = ({ selecte type: 'BulkDelete', title: `Delete Logs`, org: selectedOrg, - meta: records.queryResults.records, + meta: { records: records.queryResults.records }, }, ]; fromJetstreamEvents.emit({ type: 'newJob', payload: jobs }); diff --git a/apps/jetstream/src/app/components/debug-log-viewer/useDebugLogs.tsx b/apps/jetstream/src/app/components/debug-log-viewer/useDebugLogs.tsx index 20c187116..a4a1a1857 100644 --- a/apps/jetstream/src/app/components/debug-log-viewer/useDebugLogs.tsx +++ b/apps/jetstream/src/app/components/debug-log-viewer/useDebugLogs.tsx @@ -50,7 +50,7 @@ export function useDebugLogs(org: SalesforceOrgUi, { limit, pollInterval, userId setLogs(queryResults.records); } else { setLogs((logs) => - orderBy(Object.values({ ...getMapOf(logs, 'Id'), ...getMapOf(queryResults.records, 'Id') }), ['Id'], ['asc']) + orderBy(Object.values({ ...getMapOf(logs, 'Id'), ...getMapOf(queryResults.records, 'Id') }), ['LastModifiedDate'], ['desc']) ); } setLoading(false); diff --git a/apps/jetstream/src/app/components/deploy/DeployMetadataDeployment.tsx b/apps/jetstream/src/app/components/deploy/DeployMetadataDeployment.tsx index b9a81a55c..6852d02f5 100644 --- a/apps/jetstream/src/app/components/deploy/DeployMetadataDeployment.tsx +++ b/apps/jetstream/src/app/components/deploy/DeployMetadataDeployment.tsx @@ -1,7 +1,7 @@ import { css } from '@emotion/react'; import { ListMetadataResultItem, useListMetadata } from '@jetstream/connected-ui'; import { ANALYTICS_KEYS } from '@jetstream/shared/constants'; -import { formatNumber, transformTabularDataToExcelStr } from '@jetstream/shared/ui-utils'; +import { copyRecordsToClipboard, formatNumber } from '@jetstream/shared/ui-utils'; import { pluralizeIfMultiple } from '@jetstream/shared/utils'; import { ListMetadataResult, MapOf, SalesforceOrgUi } from '@jetstream/types'; import { @@ -17,7 +17,6 @@ import { ToolbarItemActions, ToolbarItemGroup, } from '@jetstream/ui'; -import copyToClipboard from 'copy-to-clipboard'; import addMinutes from 'date-fns/addMinutes'; import formatISODate from 'date-fns/formatISO'; import isAfter from 'date-fns/isAfter'; @@ -29,19 +28,19 @@ import { useRecoilState, useRecoilValue } from 'recoil'; import { applicationCookieState, selectedOrgState } from '../../app-state'; import { useAmplitude } from '../core/analytics'; import * as fromJetstreamEvents from '../core/jetstream-events'; +import DeployMetadataDeploymentSidePanel from './DeployMetadataDeploymentSidePanel'; +import DeployMetadataDeploymentTable from './DeployMetadataDeploymentTable'; +import DeployMetadataLastRefreshedPopover from './DeployMetadataLastRefreshedPopover'; import AddToChangeset from './add-to-changeset/AddToChangeset'; import DeleteMetadataModal from './delete-metadata/DeleteMetadataModal'; import DeployMetadataHistoryModal from './deploy-metadata-history/DeployMetadataHistoryModal'; import DeployMetadataPackage from './deploy-metadata-package/DeployMetadataPackage'; import * as fromDeployMetadataState from './deploy-metadata.state'; -import { AllUser, DeployMetadataTableRow, SidePanelType, YesNo } from './deploy-metadata.types'; +import { DeployMetadataTableRow, SidePanelType } from './deploy-metadata.types'; import DeployMetadataToOrg from './deploy-to-different-org/DeployMetadataToOrg'; -import DeployMetadataDeploymentSidePanel from './DeployMetadataDeploymentSidePanel'; -import DeployMetadataDeploymentTable from './DeployMetadataDeploymentTable'; -import DeployMetadataLastRefreshedPopover from './DeployMetadataLastRefreshedPopover'; -import { convertRowsForExport, convertRowsToMapOfListMetadataResults, getRows } from './utils/deploy-metadata.utils'; import DeployMetadataSelectedItemsBadge from './utils/DeployMetadataSelectedItemsBadge'; import DownloadPackageWithFileSelector from './utils/DownloadPackageWithFileSelector'; +import { convertRowsForExport, convertRowsToMapOfListMetadataResults, getRows } from './utils/deploy-metadata.utils'; import ViewOrCompareMetadataModal from './view-or-compare-metadata/ViewOrCompareMetadataModal'; const TABLE_ACTION_CLIPBOARD = 'table-copy-to-clipboard'; @@ -211,9 +210,9 @@ export const DeployMetadataDeployment: FunctionComponent = ({ rows, hasSelectedRows, onSelectedRows, onViewOrCompareOpen, }) => { - const [columns, setColumns] = useState[]>([]); const [visibleRows, setVisibleRows] = useState(rows); const [globalFilter, setGlobalFilter] = useState(null); const [selectedRowIds, setSelectedRowIds] = useState(new Set()); @@ -35,10 +36,6 @@ export const DeployMetadataDeploymentTable: FunctionComponent typeLabel))); }, [rows]); - useEffect(() => { - setColumns(getColumnDefinitions()); - }, []); - useEffect(() => { onSelectedRows(new Set(rows.filter((row) => selectedRowIds.has(getRowId(row))))); }, [onSelectedRows, rows, selectedRowIds]); @@ -60,8 +57,8 @@ export const DeployMetadataDeploymentTable: FunctionComponent )} - { const modalBodyRef = useRef(null); - const [{ serverUrl }] = useRecoilState(applicationCookieState); + const { serverUrl } = useRecoilValue(applicationCookieState); + const skipFrontDoorAuth = useRecoilValue(selectSkipFrontdoorAuth); const [changesetEntryType, setChangesetEntryType] = useState<'list' | 'manual'>('list'); const [changesetPackage, setChangesetPackage] = useState(initialPackage || ''); const [changesetDescription, setChangesetDescription] = useState(initialDescription || ''); @@ -105,6 +106,7 @@ export const AddToChangesetConfigModal: FunctionComponent diff --git a/apps/jetstream/src/app/components/deploy/add-to-changeset/AddToChangesetStatusModal.tsx b/apps/jetstream/src/app/components/deploy/add-to-changeset/AddToChangesetStatusModal.tsx index 1b2101806..8aa63156b 100644 --- a/apps/jetstream/src/app/components/deploy/add-to-changeset/AddToChangesetStatusModal.tsx +++ b/apps/jetstream/src/app/components/deploy/add-to-changeset/AddToChangesetStatusModal.tsx @@ -2,10 +2,10 @@ import { useNonInitialEffect } from '@jetstream/shared/ui-utils'; import { ChangeSet, DeployResult, ListMetadataResult, MapOf, SalesforceOrgUi } from '@jetstream/types'; import { SalesforceLogin } from '@jetstream/ui'; import { Fragment, FunctionComponent, useEffect, useState } from 'react'; -import { useRecoilState } from 'recoil'; -import { applicationCookieState } from '../../../app-state'; -import { getDeploymentStatusUrl, getLightningChangesetUrl } from '../utils/deploy-metadata.utils'; +import { useRecoilValue } from 'recoil'; +import { applicationCookieState, selectSkipFrontdoorAuth } from '../../../app-state'; import DeployMetadataStatusModal from '../utils/DeployMetadataStatusModal'; +import { getDeploymentStatusUrl, getLightningChangesetUrl } from '../utils/deploy-metadata.utils'; import { getStatusValue, useAddItemsToChangeset } from '../utils/useAddItemsToChangeset'; export interface AddToChangesetStatusModalProps { @@ -32,7 +32,8 @@ export const AddToChangesetStatusModal: FunctionComponent { - const [{ serverUrl }] = useRecoilState(applicationCookieState); + const { serverUrl } = useRecoilValue(applicationCookieState); + const skipFrontDoorAuth = useRecoilValue(selectSkipFrontdoorAuth); const [deployStatusUrl, setDeployStatusUrl] = useState(null); const { deployMetadata, results, deployId, loading, status, lastChecked, hasError, errorMessage } = useAddItemsToChangeset(selectedOrg, { changesetName, @@ -73,14 +74,26 @@ export const AddToChangesetStatusModal: FunctionComponent {changeset?.link && (
    - + View the outbound changeset.
    )} {deployStatusUrl && (
    - + View the deployment details.
    diff --git a/apps/jetstream/src/app/components/deploy/delete-metadata/DeleteMetadataConfigModal.tsx b/apps/jetstream/src/app/components/deploy/delete-metadata/DeleteMetadataConfigModal.tsx index 16fb42d60..3cbfd9748 100644 --- a/apps/jetstream/src/app/components/deploy/delete-metadata/DeleteMetadataConfigModal.tsx +++ b/apps/jetstream/src/app/components/deploy/delete-metadata/DeleteMetadataConfigModal.tsx @@ -89,7 +89,7 @@ export const DeleteMetadataConfigModal: FunctionComponent', - '\t', + '', `\t${defaultApiVersion.replace('v', '')}`, '', ].join('\n') diff --git a/apps/jetstream/src/app/components/deploy/delete-metadata/DeleteMetadataStatusModal.tsx b/apps/jetstream/src/app/components/deploy/delete-metadata/DeleteMetadataStatusModal.tsx index 1bc096c62..fe5fbde1d 100644 --- a/apps/jetstream/src/app/components/deploy/delete-metadata/DeleteMetadataStatusModal.tsx +++ b/apps/jetstream/src/app/components/deploy/delete-metadata/DeleteMetadataStatusModal.tsx @@ -2,10 +2,10 @@ import { useNonInitialEffect } from '@jetstream/shared/ui-utils'; import { DeployOptions, DeployResult, SalesforceOrgUi, Undefinable } from '@jetstream/types'; import { SalesforceLogin } from '@jetstream/ui'; import { Fragment, FunctionComponent, useEffect, useState } from 'react'; -import { useRecoilState } from 'recoil'; -import { applicationCookieState } from '../../../app-state'; -import { getDeploymentStatusUrl } from '../utils/deploy-metadata.utils'; +import { useRecoilValue } from 'recoil'; +import { applicationCookieState, selectSkipFrontdoorAuth } from '../../../app-state'; import DeployMetadataStatusModal from '../utils/DeployMetadataStatusModal'; +import { getDeploymentStatusUrl } from '../utils/deploy-metadata.utils'; import { getStatusValue, useDeployMetadataPackage } from '../utils/useDeployMetadataPackage'; export interface DeployMetadataPackageStatusModalProps { @@ -30,7 +30,8 @@ export const DeployMetadataPackageStatusModal: FunctionComponent { - const [{ serverUrl }] = useRecoilState(applicationCookieState); + const { serverUrl } = useRecoilValue(applicationCookieState); + const skipFrontDoorAuth = useRecoilValue(selectSkipFrontdoorAuth); const [deployStatusUrl, setDeployStatusUrl] = useState(null); const { deployMetadata, results, deployId, loading, status, lastChecked, hasError, errorMessage } = useDeployMetadataPackage( destinationOrg, @@ -68,10 +69,17 @@ export const DeployMetadataPackageStatusModal: FunctionComponent {deployStatusUrl && (
    - + View the destructive deployment details.
    diff --git a/apps/jetstream/src/app/components/deploy/deploy-metadata-history/DeployMetadataHistoryModal.tsx b/apps/jetstream/src/app/components/deploy/deploy-metadata-history/DeployMetadataHistoryModal.tsx index 9bc97d560..91bba9efc 100644 --- a/apps/jetstream/src/app/components/deploy/deploy-metadata-history/DeployMetadataHistoryModal.tsx +++ b/apps/jetstream/src/app/components/deploy/deploy-metadata-history/DeployMetadataHistoryModal.tsx @@ -1,16 +1,27 @@ import { css } from '@emotion/react'; import { logger } from '@jetstream/shared/client-logger'; import { ANALYTICS_KEYS } from '@jetstream/shared/constants'; -import { useRollbar } from '@jetstream/shared/ui-utils'; +import { hasModifierKey, isHKey, useGlobalEventHandler, useRollbar } from '@jetstream/shared/ui-utils'; import { MapOf, SalesforceDeployHistoryItem, SalesforceOrgUi } from '@jetstream/types'; -import { EmptyState, FileDownloadModal, Icon, Modal, OpenRoadIllustration, ScopedNotification, Spinner } from '@jetstream/ui'; +import { + EmptyState, + FileDownloadModal, + Icon, + KeyboardShortcut, + Modal, + OpenRoadIllustration, + ScopedNotification, + Spinner, + Tooltip, + fireToast, + getModifierKey, +} from '@jetstream/ui'; import classNames from 'classnames'; -import { Fragment, useEffect, useRef, useState } from 'react'; +import { Fragment, useCallback, useEffect, useRef, useState } from 'react'; import { useRecoilState, useRecoilValue } from 'recoil'; import * as fromAppState from '../../../app-state'; -import { useAmplitude } from '../../core/analytics'; -import { fireToast } from '../../core/AppToast'; import ConfirmPageChange from '../../core/ConfirmPageChange'; +import { useAmplitude } from '../../core/analytics'; import * as fromJetstreamEvents from '../../core/jetstream-events'; import { getDeployResultsExcelData, getHistory, getHistoryItemFile } from '../utils/deploy-metadata.utils'; import DeployMetadataHistoryTable from './DeployMetadataHistoryTable'; @@ -58,6 +69,20 @@ export const DeployMetadataHistoryModal = ({ className }: DeployMetadataHistoryM const [errorMessage, setError] = useState(null); const orgsById = useRecoilValue(fromAppState.salesforceOrgsById); + const onKeydown = useCallback( + (event: KeyboardEvent) => { + if (!isOpen && hasModifierKey(event as any) && isHKey(event as any)) { + event.stopPropagation(); + event.preventDefault(); + handleToggleOpen(true, 'keyboardShortcut'); + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [isOpen] + ); + + useGlobalEventHandler('keydown', onKeydown); + useEffect(() => { if (isOpen) { setIsLoading(true); @@ -78,10 +103,10 @@ export const DeployMetadataHistoryModal = ({ className }: DeployMetadataHistoryM } }, [isOpen]); - function handleToggleOpen(open: boolean) { + function handleToggleOpen(open: boolean, source = 'buttonClick') { setIsOpen(open); if (open) { - trackEvent(ANALYTICS_KEYS.deploy_history_opened); + trackEvent(ANALYTICS_KEYS.deploy_history_opened, source); } } @@ -162,15 +187,23 @@ export const DeployMetadataHistoryModal = ({ className }: DeployMetadataHistoryM return ( -
    + } > - - History - + + {downloadPackageModalState.open && downloadPackageModalState.org && downloadPackageModalState.data && ( [] = [ name: 'Started', key: 'start', width: 200, + draggable: true, }, { ...setColumnFromType('type', 'text'), name: 'Type', key: 'type', width: 165, - formatter: ({ column, row }) => TYPE_MAP[row[column.key]], + draggable: true, + renderCell: ({ column, row }) => TYPE_MAP[row[column.key]], }, { ...setColumnFromType('destinationOrg', 'text'), name: 'Deployed To Org', key: 'destinationOrg', - formatter: OrgRenderer, + draggable: true, + renderCell: OrgRenderer, getValue: ({ row }) => row.destinationOrg?.label, width: 350, }, @@ -44,7 +46,8 @@ const COLUMNS: ColumnWithFilter[] = [ ...setColumnFromType('status', 'text'), name: 'Status', key: 'status', - formatter: StatusRenderer, + draggable: true, + renderCell: StatusRenderer, width: 150, }, { @@ -53,26 +56,23 @@ const COLUMNS: ColumnWithFilter[] = [ width: 220, sortable: false, resizable: false, - formatter: ActionRenderer, + draggable: true, + renderCell: ActionRenderer, }, ]; -const getRowHeight = - (orgsById: MapOf) => - ({ row: item, type }: RowHeightArgs) => { - const rowHeight = 27.5; - let numberOfRows = 3; - if (type === 'ROW') { - if (item.type === 'orgToOrg') { - /** we need 3 rows plus a little buffer */ - numberOfRows = 3.5; - } else if (item.fileKey || (item.sourceOrg && orgsById[item.sourceOrg.uniqueId])) { - /** we need 3 rows */ - return 27.5 * 3; - } - } - return rowHeight * numberOfRows; - }; +const getRowHeight = (orgsById: MapOf) => (row: SalesforceDeployHistoryItem) => { + const rowHeight = 27.5; + let numberOfRows = 3; + if (row.type === 'orgToOrg') { + /** we need 3 rows plus a little buffer */ + numberOfRows = 3.5; + } else if (row.fileKey || (row.sourceOrg && orgsById[row.sourceOrg.uniqueId])) { + /** we need 3 rows */ + return 27.5 * 3; + } + return rowHeight * numberOfRows; +}; const getRowId = ({ key }: SalesforceDeployHistoryItem) => key; export interface DeployMetadataHistoryTableProps { @@ -95,7 +95,7 @@ export const DeployMetadataHistoryTable: FunctionComponent getRowHeight(orgsById), [orgsById]); - return ; + return ; }; export default DeployMetadataHistoryTable; diff --git a/apps/jetstream/src/app/components/deploy/deploy-metadata-history/DeployMetadataHistoryTableRenderers.tsx b/apps/jetstream/src/app/components/deploy/deploy-metadata-history/DeployMetadataHistoryTableRenderers.tsx index db5918ffb..ba7b635e7 100644 --- a/apps/jetstream/src/app/components/deploy/deploy-metadata-history/DeployMetadataHistoryTableRenderers.tsx +++ b/apps/jetstream/src/app/components/deploy/deploy-metadata-history/DeployMetadataHistoryTableRenderers.tsx @@ -3,13 +3,13 @@ import { IconName } from '@jetstream/icon-factory'; import { SalesforceDeployHistoryItem } from '@jetstream/types'; import { DataTableGenericContext, Grid, Icon } from '@jetstream/ui'; import { Fragment, useContext } from 'react'; -import { FormatterProps } from 'react-data-grid'; +import { RenderCellProps } from 'react-data-grid'; import OrgLabelBadge from '../../core/OrgLabelBadge'; import { DeployHistoryTableContext } from '../deploy-metadata.types'; const fallbackLabel = 'Unknown Org'; -export function OrgRenderer({ row: item }: FormatterProps) { +export function OrgRenderer({ row: item }: RenderCellProps) { const { orgsById } = useContext(DataTableGenericContext) as DeployHistoryTableContext; const sourceOrg = item.sourceOrg ? orgsById[item.sourceOrg.uniqueId] : null; @@ -54,7 +54,7 @@ export function OrgRenderer({ row: item }: FormatterProps) { +export function StatusRenderer({ row: item }: RenderCellProps) { let status: string = item.status; let icon: IconName = 'success'; let iconClassName = 'slds-icon slds-icon_x-small slds-icon-text-success'; @@ -82,7 +82,7 @@ export function StatusRenderer({ row: item }: FormatterProps) { +export function ActionRenderer({ row: item }: RenderCellProps) { const { onDownload, onView } = useContext(DataTableGenericContext) as DeployHistoryTableContext; return ( diff --git a/apps/jetstream/src/app/components/deploy/deploy-metadata-history/DeployMetadataHistoryViewResults.tsx b/apps/jetstream/src/app/components/deploy/deploy-metadata-history/DeployMetadataHistoryViewResults.tsx index 8bfcb810a..068a091ef 100644 --- a/apps/jetstream/src/app/components/deploy/deploy-metadata-history/DeployMetadataHistoryViewResults.tsx +++ b/apps/jetstream/src/app/components/deploy/deploy-metadata-history/DeployMetadataHistoryViewResults.tsx @@ -38,7 +38,7 @@ export const DeployMetadataHistoryViewResults: FunctionComponent {item.url && ( - + View the deployment details. )} diff --git a/apps/jetstream/src/app/components/deploy/deploy-metadata-package/DeployMetadataPackageConfigModal.tsx b/apps/jetstream/src/app/components/deploy/deploy-metadata-package/DeployMetadataPackageConfigModal.tsx index 5e47443bd..2c7d74ee7 100644 --- a/apps/jetstream/src/app/components/deploy/deploy-metadata-package/DeployMetadataPackageConfigModal.tsx +++ b/apps/jetstream/src/app/components/deploy/deploy-metadata-package/DeployMetadataPackageConfigModal.tsx @@ -108,7 +108,7 @@ export const DeployMetadataPackageConfigModal: FunctionComponent { - const [{ serverUrl }] = useRecoilState(applicationCookieState); + const { serverUrl } = useRecoilValue(applicationCookieState); + const skipFrontDoorAuth = useRecoilValue(selectSkipFrontdoorAuth); const [deployStatusUrl, setDeployStatusUrl] = useState(null); const { deployMetadata, results, deployId, loading, status, lastChecked, hasError, errorMessage } = useDeployMetadataPackage( destinationOrg, @@ -65,7 +66,13 @@ export const DeployMetadataPackageStatusModal: FunctionComponent {deployStatusUrl && (
    - + View the deployment details.
    diff --git a/apps/jetstream/src/app/components/deploy/deploy-to-different-org/DeployMetadataToOrgStatusModal.tsx b/apps/jetstream/src/app/components/deploy/deploy-to-different-org/DeployMetadataToOrgStatusModal.tsx index 83c22476a..bc79288e7 100644 --- a/apps/jetstream/src/app/components/deploy/deploy-to-different-org/DeployMetadataToOrgStatusModal.tsx +++ b/apps/jetstream/src/app/components/deploy/deploy-to-different-org/DeployMetadataToOrgStatusModal.tsx @@ -2,10 +2,10 @@ import { useNonInitialEffect } from '@jetstream/shared/ui-utils'; import { DeployOptions, DeployResult, ListMetadataResult, MapOf, SalesforceOrgUi, Undefinable } from '@jetstream/types'; import { SalesforceLogin } from '@jetstream/ui'; import { Fragment, FunctionComponent, useEffect, useState } from 'react'; -import { useRecoilState } from 'recoil'; -import { applicationCookieState } from '../../../app-state'; -import { getDeploymentStatusUrl } from '../utils/deploy-metadata.utils'; +import { useRecoilValue } from 'recoil'; +import { applicationCookieState, selectSkipFrontdoorAuth } from '../../../app-state'; import DeployMetadataStatusModal from '../utils/DeployMetadataStatusModal'; +import { getDeploymentStatusUrl } from '../utils/deploy-metadata.utils'; import { getStatusValue, useDeployMetadataBetweenOrgs } from '../utils/useDeployMetadataBetweenOrgs'; export interface DeployMetadataToOrgStatusModalProps { @@ -32,7 +32,8 @@ export const DeployMetadataToOrgStatusModal: FunctionComponent { - const [{ serverUrl }] = useRecoilState(applicationCookieState); + const { serverUrl } = useRecoilValue(applicationCookieState); + const skipFrontDoorAuth = useRecoilValue(selectSkipFrontdoorAuth); const [deployStatusUrl, setDeployStatusUrl] = useState(null); const { deployMetadata, results, deployId, loading, status, lastChecked, hasError, errorMessage } = useDeployMetadataBetweenOrgs( sourceOrg, @@ -64,10 +65,17 @@ export const DeployMetadataToOrgStatusModal: FunctionComponent {deployStatusUrl && (
    - + View the deployment details.
    diff --git a/apps/jetstream/src/app/components/deploy/selection-components/DateSelection.tsx b/apps/jetstream/src/app/components/deploy/selection-components/DateSelection.tsx index 10a3b1d32..fd4f08ee3 100644 --- a/apps/jetstream/src/app/components/deploy/selection-components/DateSelection.tsx +++ b/apps/jetstream/src/app/components/deploy/selection-components/DateSelection.tsx @@ -4,7 +4,6 @@ import addDays from 'date-fns/addDays'; import isAfter from 'date-fns/isAfter'; import isSameDay from 'date-fns/isSameDay'; import { Fragment, FunctionComponent, useEffect, useState } from 'react'; -import { CSSTransition } from 'react-transition-group'; import { useRecoilState } from 'recoil'; import * as fromDeployMetadataState from '../deploy-metadata.state'; import { AllUser } from '../deploy-metadata.types'; @@ -104,43 +103,41 @@ export const DateSelection: FunctionComponent setDateRangeSelection(value)} />
    - -
    - {dateRangeSelection === 'user' && ( - - - - - )} -
    -
    +
    + {dateRangeSelection === 'user' && ( + + + + + )} +
    ); }; diff --git a/apps/jetstream/src/app/components/deploy/utils/deploy-metadata.utils.tsx b/apps/jetstream/src/app/components/deploy/utils/deploy-metadata.utils.tsx index 2eb06bbdd..28b7656bb 100644 --- a/apps/jetstream/src/app/components/deploy/utils/deploy-metadata.utils.tsx +++ b/apps/jetstream/src/app/components/deploy/utils/deploy-metadata.utils.tsx @@ -29,7 +29,7 @@ import type { DeployOptions } from 'jsforce'; import JSZip from 'jszip'; import localforage from 'localforage'; import isString from 'lodash/isString'; -import { SelectColumn, SELECT_COLUMN_KEY } from 'react-data-grid'; +import { SELECT_COLUMN_KEY, SelectColumn } from 'react-data-grid'; import { composeQuery, getField, Query } from 'soql-parser-js'; import { DeployMetadataTableRow } from '../deploy-metadata.types'; @@ -228,7 +228,7 @@ export function getColumnDefinitions(): ColumnWithFilter ...SelectColumn, key: SELECT_COLUMN_KEY, resizable: false, - formatter: (args) => { + renderCell: (args) => { const { row } = args; if (row.loading) { return null; @@ -241,8 +241,8 @@ export function getColumnDefinitions(): ColumnWithFilter } return ; }, - headerRenderer: SelectHeaderRenderer, - groupFormatter: (args) => { + renderHeaderCell: SelectHeaderRenderer, + renderGroupCell: (args) => { const { childRows } = args; // Don't allow selection if child rows are loading if (childRows.length === 0 || (childRows.length === 1 && (childRows[0].loading || !childRows[0].metadata))) { @@ -266,7 +266,7 @@ export function getColumnDefinitions(): ColumnWithFilter key: 'typeLabel', width: 40, frozen: true, - groupFormatter: ({ isExpanded }) => ( + renderGroupCell: ({ isExpanded }) => ( name: 'Name', key: 'fullName', frozen: true, - formatter: ({ row }) => (row.loading ? : row.fullName), - groupFormatter: ({ toggleGroup, groupKey, childRows }) => ( + renderCell: ({ row }) => (row.loading ? : row.fullName), + renderGroupCell: ({ toggleGroup, groupKey, childRows }) => ( <> + )} {sourceLoading && (
    '}`}> Loading metadata from Salesforce {sourceLastChecked && - last checked at {sourceLastChecked.toLocaleTimeString()}} diff --git a/apps/jetstream/src/app/components/feedback/FeedbackForm.tsx b/apps/jetstream/src/app/components/feedback/FeedbackForm.tsx index 135050321..4dadd8d1d 100644 --- a/apps/jetstream/src/app/components/feedback/FeedbackForm.tsx +++ b/apps/jetstream/src/app/components/feedback/FeedbackForm.tsx @@ -41,7 +41,7 @@ export const FeedbackForm: FunctionComponent = () => {

    Support the Jetstream project

    -

    Jetstream is an open source project and is paid for and supported by volunteers.

    +

    Jetstream is source-available project and is paid for and supported by the community.

    = () => const [errorMessage, setErrorMessage] = useState(null); const [formulaValue, setFormulaValue] = useRecoilState(fromFormulaState.formulaValueState); const [selectedSObject, setSelectedSobject] = useRecoilState(fromFormulaState.selectedSObjectState); + const [selectedUserId, setSelectedUserId] = useRecoilState(fromFormulaState.selectedUserState); const [selectedField, setSelectedField] = useRecoilState(fromFormulaState.selectedFieldState); const [sourceType, setSourceType] = useRecoilState(fromFormulaState.sourceTypeState); const [recordId, setRecordId] = useRecoilState(fromFormulaState.recordIdState); @@ -88,10 +90,14 @@ export const FormulaEvaluator: FunctionComponent = () => const [results, setResults] = useState<{ formulaFields: formulon.FormulaData; parsedFormula: formulon.FormulaResult } | null>(null); const deployFormulaDisabled = loading || !selectedSObject || !formulaValue; - const testFormulaDisabled = loading || !selectedSObject || !recordId || !formulaValue; + const testFormulaDisabled = loading || !selectedSObject || !selectedUserId || !recordId || !formulaValue; const monaco = useMonaco(); + useEffect(() => { + setSelectedUserId(selectedOrg.userId); + }, [selectedOrg.userId, setSelectedUserId]); + useEffect(() => { isMounted.current = true; return () => { @@ -129,6 +135,7 @@ export const FormulaEvaluator: FunctionComponent = () => fields, recordId, selectedOrg, + selectedUserId, sobjectName: selectedSObject?.name || '', numberNullBehavior, }); @@ -159,6 +166,7 @@ export const FormulaEvaluator: FunctionComponent = () => const onKeydown = useCallback( (event: KeyboardEvent) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any if (!testFormulaDisabled && hasModifierKey(event as any) && isEnterKey(event as any)) { event.stopPropagation(); event.preventDefault(); @@ -283,6 +291,7 @@ export const FormulaEvaluator: FunctionComponent = () =>
    Formula Evaluator
    @@ -310,6 +319,7 @@ export const FormulaEvaluator: FunctionComponent = () => filterFn={filterSobjectFn} onSelectedSObject={setSelectedSobject} /> + = () =>
    Results
    } actions={ <> @@ -431,7 +442,6 @@ export const FormulaEvaluator: FunctionComponent = () => } > {loading && } - = () => fieldErrorMessage={fieldErrorMessage} onSelectedRecord={setRecordId} /> - {errorMessage && ( -
    - - {errorMessage} - -
    - )} - {results && ( - - {!!Object.keys(results.formulaFields).length && ( - <> -
    Record Fields
    -
    - {Object.keys(results.formulaFields).map((field) => { - const { value } = results.formulaFields[field]; - return ( -
    - {field}: {String(value) || ''} -
    - ); - })} -
    - - )} -
    Formula Results
    -
    - {results.parsedFormula.type === 'error' ? ( - -
    {results.parsedFormula.errorType}
    -
    {results.parsedFormula.message}
    - {results.parsedFormula.errorType === 'NotImplementedError' && results.parsedFormula.name === 'isnull' && ( -
    Use ISBLANK instead
    - )} -
    - ) : ( -
    {String(results.parsedFormula.value)}
    - )} -
    -
    - )} + {!errorMessage && !results && ( ), + titleText: 'Field', content: , }, { @@ -267,6 +267,7 @@ export const FormulaEvaluatorDeployModal = ({ Field Permissions ({selectedProfiles.length + selectedPermissionSets.length}) ), + titleText: 'Field Permissions', content: ( Page Layouts ({layoutData.selectedLayoutIds.size}) ), + titleText: 'Page Layouts', content: ( ), + titleText: 'Deploy Field', content: ( = {}; let layoutResults: MapOf = {}; @@ -68,6 +69,7 @@ export function FormulaEvaluatorDeploySummary({ diff --git a/apps/jetstream/src/app/components/formula-evaluator/formula-evaluator.editor-utils.ts b/apps/jetstream/src/app/components/formula-evaluator/formula-evaluator.editor-utils.ts index df8459b6a..8a614c065 100644 --- a/apps/jetstream/src/app/components/formula-evaluator/formula-evaluator.editor-utils.ts +++ b/apps/jetstream/src/app/components/formula-evaluator/formula-evaluator.editor-utils.ts @@ -1,7 +1,7 @@ import { logger } from '@jetstream/shared/client-logger'; import { describeGlobal, describeSObject, manualRequest, queryAllWithCache } from '@jetstream/shared/data'; import { SalesforceOrgUi } from '@jetstream/types'; -import type { DescribeSObjectResult } from 'jsforce'; +import type { DescribeSObjectResult, Field } from 'jsforce'; import type * as monaco from 'monaco-editor'; import { composeQuery, getField } from 'soql-parser-js'; import { CharacterInfo, CustomPermission, ExternalString, SpecialWordType } from './formula-evaluator.types'; @@ -27,7 +27,12 @@ const triggerChars = 'adefhijlmnsu'; * Register completions only for fields * Formula completions are registered in monaco-sfdx-formula-completions since these are known ahead of time */ -export async function registerCompletions(monaco: Monaco, selectedOrg: SalesforceOrgUi, sobject?: string) { +export async function registerCompletions( + monaco: Monaco, + selectedOrg: SalesforceOrgUi, + sobject?: string, + additionalFields?: Partial[] +) { if (priorCompletion) { priorCompletion.dispose(); } @@ -38,7 +43,7 @@ export async function registerCompletions(monaco: Monaco, selectedOrg: Salesforc provideCompletionItems: async (model, position, context, token) => { const characterInfo = getCharacterInfo(model, position); return { - suggestions: await fetchCompletions(monaco, characterInfo, selectedOrg, sobject), + suggestions: await fetchCompletions(monaco, characterInfo, selectedOrg, sobject, additionalFields), }; }, }); @@ -90,7 +95,8 @@ async function fetchCompletions( monaco: Monaco, characterInfo: CharacterInfo, selectedOrg: SalesforceOrgUi, - sobject?: string + sobject?: string, + additionalFields?: Partial[] ): Promise { const { textUntilPosition: textUntilPositionAll, mostRecentCharacter, range } = characterInfo; // if spaces, ignore prior words - e.x. "Log__r.ApiVersion__c != LoggedBy__r" we only care about LoggedBy__r @@ -106,7 +112,7 @@ async function fetchCompletions( } } - let currentSObjectMeta: DescribeSObjectResult; + let currentSObjectMeta: Omit & { fields: Partial[] }; /** * Handle all special words (starts with `$`) @@ -158,8 +164,8 @@ async function fetchCompletions( if (!foundCustomMetadata) { return []; } - const { queryResults } = await queryAllWithCache(selectedOrg, `SELECT Id, QualifiedApiName FROM ${foundCustomMetadata.name}`); - return queryResults.records.map(({ QualifiedApiName }) => ({ + const { data } = await queryAllWithCache(selectedOrg, `SELECT Id, QualifiedApiName FROM ${foundCustomMetadata.name}`); + return data.queryResults.records.map(({ QualifiedApiName }) => ({ label: QualifiedApiName, filterText: QualifiedApiName, kind: monaco.languages.CompletionItemKind.Class, @@ -221,7 +227,10 @@ async function fetchCompletions( */ const { data } = await describeSObject(selectedOrg, sobject); // Current object metadata - will be changed when fetching related object metadata - currentSObjectMeta = data; + currentSObjectMeta = { + ...data, + fields: [...data.fields, ...(additionalFields || [])], + }; // If true, then special keywords will be included in results let isRootObject = true; // Completions are added to the list @@ -265,7 +274,7 @@ async function fetchCompletions( detail: field.type, filterText: field.name, kind: monaco.languages.CompletionItemKind.Class, - insertText: field.name, + insertText: field.name!, range, }); if (!!field.relationshipName && !!field.referenceTo?.length) { @@ -297,7 +306,7 @@ async function fetchAndNormalizeLabelOrPermission( }[] > { if (type === 'customLabel') { - const { queryResults } = await queryAllWithCache( + const { data } = await queryAllWithCache( selectedOrg, composeQuery({ fields: [getField('Name'), getField('MasterLabel'), getField('NamespacePrefix'), getField('Value')], @@ -314,13 +323,13 @@ async function fetchAndNormalizeLabelOrPermission( true ); - return queryResults.records.map(({ Name, NamespacePrefix, MasterLabel, Value }) => ({ + return data.queryResults.records.map(({ Name, NamespacePrefix, MasterLabel, Value }) => ({ name: NamespacePrefix ? `${NamespacePrefix}__${Name}` : Name, label: MasterLabel, detail: Value, })); } else if (type === 'customPermission') { - const { queryResults } = await queryAllWithCache( + const { data } = await queryAllWithCache( selectedOrg, composeQuery({ fields: [getField('DeveloperName'), getField('MasterLabel'), getField('NamespacePrefix'), getField('Description')], @@ -336,7 +345,7 @@ async function fetchAndNormalizeLabelOrPermission( }) ); - return queryResults.records.map(({ DeveloperName, NamespacePrefix, MasterLabel, Description }) => ({ + return data.queryResults.records.map(({ DeveloperName, NamespacePrefix, MasterLabel, Description }) => ({ name: NamespacePrefix ? `${NamespacePrefix}__${DeveloperName}` : DeveloperName, label: MasterLabel, detail: Description, diff --git a/apps/jetstream/src/app/components/formula-evaluator/formula-evaluator.state.ts b/apps/jetstream/src/app/components/formula-evaluator/formula-evaluator.state.ts index 34fae21d2..159efa297 100644 --- a/apps/jetstream/src/app/components/formula-evaluator/formula-evaluator.state.ts +++ b/apps/jetstream/src/app/components/formula-evaluator/formula-evaluator.state.ts @@ -13,6 +13,11 @@ export const selectedSObjectState = atom({ default: null, }); +export const selectedUserState = atom({ + key: 'formula.selectedUserState', + default: null, +}); + export const selectedFieldState = atom({ key: 'formula.selectedFieldState', default: null, diff --git a/apps/jetstream/src/app/components/home/AppHome.tsx b/apps/jetstream/src/app/components/home/AppHome.tsx index 149a929b2..da33b6f2f 100644 --- a/apps/jetstream/src/app/components/home/AppHome.tsx +++ b/apps/jetstream/src/app/components/home/AppHome.tsx @@ -1,4 +1,5 @@ import { css } from '@emotion/react'; +import { IconName, IconType } from '@jetstream/icon-factory'; import { Badge, Icon } from '@jetstream/ui'; import classNames from 'classnames'; import { Fragment } from 'react'; @@ -76,10 +77,13 @@ export const AppHome = () => {
    {card.icon && ( )}
    diff --git a/apps/jetstream/src/app/components/load-records-multi-object/LoadRecordsMultiObject.tsx b/apps/jetstream/src/app/components/load-records-multi-object/LoadRecordsMultiObject.tsx index d3e0ada73..af48f708a 100644 --- a/apps/jetstream/src/app/components/load-records-multi-object/LoadRecordsMultiObject.tsx +++ b/apps/jetstream/src/app/components/load-records-multi-object/LoadRecordsMultiObject.tsx @@ -18,12 +18,12 @@ import { ScopedNotification, Select, Spinner, + fireToast, } from '@jetstream/ui'; import { ChangeEvent, FunctionComponent, useEffect, useMemo, useRef, useState } from 'react'; import { useRecoilState, useRecoilValue } from 'recoil'; import * as XLSX from 'xlsx'; import { applicationCookieState, selectedOrgState, selectedOrgType } from '../../app-state'; -import { fireToast } from '../core/AppToast'; import { useAmplitude } from '../core/analytics'; import { LocalOrGoogle } from '../load-records/load-records-types'; import LoadRecordsMultiObjectErrors from './LoadRecordsMultiObjectErrors'; @@ -213,7 +213,7 @@ export const LoadRecordsMultiObject: FunctionComponent; externalIdValue: string | null; recordIdForUpdate: string | null; dependencies: string[] }, header ) => { - let externalIdValue: string | null = null; + const isRelatedField = header.includes('.'); const field = dataset.fieldsByName[header.toLowerCase()]; let value = record[header]; const valueIsNull = isNil(value) || (isString(value) && !value); @@ -423,7 +424,7 @@ export function getDataGraph( } else if (insertNulls) { transformedRecord[field.name] = null; } - } else if (dataset.operation === 'UPSERT' && lowercaseExternalId === field.name.toLowerCase()) { + } else if (dataset.operation === 'UPSERT' && !isRelatedField && lowercaseExternalId === field.name.toLowerCase()) { // External id used for upsert, this needs to be omitted from the record as the value is included in the URL externalIdValue = value; } else if (insertNulls || !valueIsNull) { @@ -443,7 +444,7 @@ export function getDataGraph( } // for updates, we need to know the url to update the record - this could be a relationship id or hard-coded id - if (dataset.operation === 'UPDATE' && field.name.toLowerCase() === 'id') { + if (dataset.operation === 'UPDATE' && !isRelatedField && field.name.toLowerCase() === 'id') { recordIdForUpdate = transformedRecord[field.name]; } diff --git a/apps/jetstream/src/app/components/load-records/LoadRecords.tsx b/apps/jetstream/src/app/components/load-records/LoadRecords.tsx index 214c4258d..b7d025a2e 100644 --- a/apps/jetstream/src/app/components/load-records/LoadRecords.tsx +++ b/apps/jetstream/src/app/components/load-records/LoadRecords.tsx @@ -18,7 +18,7 @@ import { } from '@jetstream/ui'; import startCase from 'lodash/startCase'; import { FunctionComponent, useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil'; +import { useRecoilState, useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil'; import { applicationCookieState, selectedOrgState, selectedOrgType } from '../../app-state'; import { useAmplitude } from '../core/analytics'; import LoadRecordsDataPreview from './components/LoadRecordsDataPreview'; @@ -31,7 +31,7 @@ import LoadRecordsLoadAutomationRollback from './steps/LoadRecordsAutomationRoll import PerformLoad from './steps/PerformLoad'; import PerformLoadCustomMetadata from './steps/PerformLoadCustomMetadata'; import LoadRecordsSelectObjectAndFile from './steps/SelectObjectAndFile'; -import { autoMapFields, getFieldMetadata } from './utils/load-records-utils'; +import { autoMapFields, getFieldMetadata, getMaxBatchSize, getRecommendedApiMode } from './utils/load-records-utils'; const HEIGHT_BUFFER = 170; @@ -83,6 +83,10 @@ export const LoadRecords: FunctionComponent = ({ featureFlags const [fieldMapping, setFieldMapping] = useRecoilState(fromLoadRecordsState.fieldMappingState); + const setApiMode = useSetRecoilState(fromLoadRecordsState.apiModeState); + const setBatchSize = useSetRecoilState(fromLoadRecordsState.batchSizeState); + const setSerialMode = useSetRecoilState(fromLoadRecordsState.serialModeState); + const [loading, setLoading] = useState(false); const [loadingFields, setLoadingFields] = useState(false); const [didPerformDataLoad, setDidPerformDataLoad] = useState(false); @@ -105,6 +109,13 @@ export const LoadRecords: FunctionComponent = ({ featureFlags const resetInputZipFileData = useResetRecoilState(fromLoadRecordsState.inputZipFileDataState); const resetInputZipFilename = useResetRecoilState(fromLoadRecordsState.inputZipFilenameState); + const resetApiModeState = useResetRecoilState(fromLoadRecordsState.apiModeState); + const resetBatchSizeState = useResetRecoilState(fromLoadRecordsState.batchSizeState); + const resetInsertNullsState = useResetRecoilState(fromLoadRecordsState.insertNullsState); + const resetSerialModeState = useResetRecoilState(fromLoadRecordsState.serialModeState); + const resetTrialRunState = useResetRecoilState(fromLoadRecordsState.trialRunState); + const resetTrialRunSizeState = useResetRecoilState(fromLoadRecordsState.trialRunSizeState); + useEffect(() => { isMounted.current = true; return () => { @@ -119,6 +130,28 @@ export const LoadRecords: FunctionComponent = ({ featureFlags } }, [isCustomMetadataObject, setLoadType]); + // On file change, reset load option state + useEffect(() => { + const apiMode = getRecommendedApiMode(inputFileData?.length || 0, !!inputZipFileData); + setApiMode(apiMode); + setBatchSize(getMaxBatchSize(apiMode)); + setSerialMode(apiMode === 'BATCH'); + + resetInsertNullsState(); + resetTrialRunState(); + resetTrialRunSizeState(); + }, [ + inputFileData, + inputZipFileData, + resetBatchSizeState, + resetTrialRunSizeState, + resetTrialRunState, + resetInsertNullsState, + setApiMode, + setBatchSize, + setSerialMode, + ]); + // reset state when user leaves page useEffect(() => { return () => { @@ -132,6 +165,12 @@ export const LoadRecords: FunctionComponent = ({ featureFlags resetFieldMappingTypeState(); resetInputZipFileData(); resetInputZipFilename(); + resetApiModeState(); + resetBatchSizeState(); + resetInsertNullsState(); + resetSerialModeState(); + resetTrialRunState(); + resetTrialRunSizeState(); } }; }, [ @@ -145,6 +184,12 @@ export const LoadRecords: FunctionComponent = ({ featureFlags resetFieldMappingTypeState, resetInputZipFileData, resetInputZipFilename, + resetApiModeState, + resetBatchSizeState, + resetInsertNullsState, + resetSerialModeState, + resetTrialRunState, + resetTrialRunSizeState, ]); useEffect(() => { @@ -196,12 +241,12 @@ export const LoadRecords: FunctionComponent = ({ featureFlags useEffect(() => { if (mappableFields && inputFileHeader) { - setFieldMapping(autoMapFields(inputFileHeader, mappableFields, binaryAttachmentBodyField)); + setFieldMapping(autoMapFields(inputFileHeader, mappableFields, binaryAttachmentBodyField, loadType, externalId)); } - }, [mappableFields, inputFileHeader, loadType, setFieldMapping, binaryAttachmentBodyField]); + }, [mappableFields, inputFileHeader, loadType, setFieldMapping, binaryAttachmentBodyField, externalId]); useEffect(() => { - setExternalIdFields(fields.filter((field) => field.externalId)); + setExternalIdFields(fields.filter((field) => field.name === 'Id' || field.externalId)); }, [fields]); useEffect(() => { @@ -334,6 +379,12 @@ export const LoadRecords: FunctionComponent = ({ featureFlags resetInputFilenameState(); resetFieldMappingState(); setExternalId(''); + resetApiModeState(); + resetBatchSizeState(); + resetInsertNullsState(); + resetSerialModeState(); + resetTrialRunState(); + resetTrialRunSizeState(); trackEvent(ANALYTICS_KEYS.load_StartOver, { page: currentStep.name }); } diff --git a/apps/jetstream/src/app/components/load-records/components/LoadRecordsDataPreview.tsx b/apps/jetstream/src/app/components/load-records/components/LoadRecordsDataPreview.tsx index cd57917bb..ce1a6dde1 100644 --- a/apps/jetstream/src/app/components/load-records/components/LoadRecordsDataPreview.tsx +++ b/apps/jetstream/src/app/components/load-records/components/LoadRecordsDataPreview.tsx @@ -9,9 +9,12 @@ import type { DescribeGlobalSObjectResult } from 'jsforce'; import isNil from 'lodash/isNil'; import { FunctionComponent, useEffect, useRef, useState } from 'react'; import { Column } from 'react-data-grid'; +import { ErrorBoundary } from 'react-error-boundary'; import { useRecoilState } from 'recoil'; +import ErrorBoundaryFallback from '../../core/ErrorBoundaryFallback'; import * as fromLoadRecordsState from '../load-records.state'; +const MAX_RECORD_FOR_PREVIEW = 100_000; const MAX_COLUMNS_TO_KEEP_SET_FILTER = 2000; const NUM_COLUMN = '_num'; const getRowId = ({ _num }: any) => _num; @@ -121,7 +124,7 @@ export const LoadRecordsDataPreview: FunctionComponent { - if (data && header) { + if (data && header && data.length < MAX_RECORD_FOR_PREVIEW) { // Transform data keys if needed to ensure the table preview can be rendered // Special characters in the header key cause issues with react-data-grid let _rows = data; @@ -151,6 +154,8 @@ export const LoadRecordsDataPreview: FunctionComponent MAX_RECORD_FOR_PREVIEW; + return (
    @@ -176,7 +181,8 @@ export const LoadRecordsDataPreview: FunctionComponent - {columns && rows && ( + {tooLargeToShowPreview &&

    Your file is too large to show a preview

    } + {Array.isArray(columns) && Array.isArray(rows) && (
    -
    File Preview
    - - - +

    File Preview

    + + + + +
    )}
    diff --git a/apps/jetstream/src/app/components/load-records/components/LoadRecordsFieldMappingRow.tsx b/apps/jetstream/src/app/components/load-records/components/LoadRecordsFieldMappingRow.tsx index d487c38c7..99ddff32b 100644 --- a/apps/jetstream/src/app/components/load-records/components/LoadRecordsFieldMappingRow.tsx +++ b/apps/jetstream/src/app/components/load-records/components/LoadRecordsFieldMappingRow.tsx @@ -5,7 +5,7 @@ import { Checkbox, ComboboxWithItems, Grid, Icon, Select } from '@jetstream/ui'; import classNames from 'classnames'; import isNil from 'lodash/isNil'; import { Fragment, FunctionComponent, useEffect, useState } from 'react'; -import { FieldMappingItem, FieldRelatedEntity, FieldWithRelatedEntities } from '../load-records-types'; +import { FieldMappingItem, FieldMappingItemCsv, FieldRelatedEntity, FieldWithRelatedEntities } from '../load-records-types'; import { SELF_LOOKUP_KEY } from '../utils/load-records-utils'; import LoadRecordsFieldMappingRowLookupOption from './LoadRecordsFieldMappingRowLookupOption'; @@ -30,11 +30,11 @@ function getComboboxFieldTitle(item: ListItem) { export interface LoadRecordsFieldMappingRowProps { isCustomMetadataObject: boolean; fields: FieldWithRelatedEntities[]; - fieldMappingItem: FieldMappingItem; + fieldMappingItem: FieldMappingItemCsv; csvField: string; csvRowData: string; binaryAttachmentBodyField?: string; - onSelectionChanged: (csvField: string, fieldMappingItem: FieldMappingItem) => void; + onSelectionChanged: (csvField: string, fieldMappingItem: FieldMappingItemCsv) => void; } function getFieldListItems(fields: FieldWithRelatedEntities[]) { @@ -103,6 +103,7 @@ export const LoadRecordsFieldMappingRow: FunctionComponent) { if (!field) { onSelectionChanged(csvField, { + type: 'CSV', csvField, targetField: null, mappedToLookup: false, @@ -172,8 +173,20 @@ export const LoadRecordsFieldMappingRow: FunctionComponent - -
    + +
    {csvRowDataStr}
    @@ -204,9 +217,9 @@ export const LoadRecordsFieldMappingRow: FunctionComponent diff --git a/apps/jetstream/src/app/components/load-records/components/LoadRecordsFieldMappingRowLookupOption.tsx b/apps/jetstream/src/app/components/load-records/components/LoadRecordsFieldMappingRowLookupOption.tsx index bb55979ab..5b939f5c8 100644 --- a/apps/jetstream/src/app/components/load-records/components/LoadRecordsFieldMappingRowLookupOption.tsx +++ b/apps/jetstream/src/app/components/load-records/components/LoadRecordsFieldMappingRowLookupOption.tsx @@ -20,7 +20,7 @@ export const LoadRecordsFieldMappingRowLookupOption: FunctionComponent void; + onRemoveRow: () => void; +} + +function getFieldListItems(fields: FieldWithRelatedEntities[]) { + return fields.map((field) => ({ + id: field.name, + label: field.label, + value: field.name, + secondaryLabel: field.typeLabel, + meta: field, + customRenderer: (item: ListItem) => ( + <> + + + + {item.label} + + {item.secondaryLabel && ( + + {item.secondaryLabel} + + )} + + + + + {item.value} + + + + ), + })); +} + +export const LoadRecordsFieldMappingStaticRow: FunctionComponent = ({ + fields, + fieldMappingItem, + isCustomMetadata, + onSelectionChanged, + onRemoveRow, +}) => { + const errorId = useId(); + const [fieldListItems, setFieldListItems] = useState[]>(() => getFieldListItems(fields)); + const [editableField, setEditableField] = useState>(null); + + useNonInitialEffect(() => { + setFieldListItems(getFieldListItems(fields)); + }, [fields]); + + useEffect(() => { + if (fieldMappingItem.fieldMetadata?.field) { + const picklistValues: PicklistFieldValues = {}; + if (fieldMappingItem.fieldMetadata.field.type === 'picklist' || fieldMappingItem.fieldMetadata.field.type === 'multipicklist') { + picklistValues[fieldMappingItem.fieldMetadata.field.name] = { + controllerValues: {}, + defaultValue: fieldMappingItem.fieldMetadata.field.picklistValues?.find((value) => value.defaultValue)?.value, + eTag: '', + url: '', + values: + fieldMappingItem.fieldMetadata.field.picklistValues?.map(({ value, label }) => ({ + value, + label: label || value, + attributes: null, + validFor: null, + })) || [], + }; + } + setEditableField( + convertMetadataToEditableFields([fieldMappingItem.fieldMetadata.field], picklistValues, 'create', {}, isCustomMetadata)[0] + ); + } + }, [fieldMappingItem.fieldMetadata, isCustomMetadata]); + + function handleValueChange(field: EditableFields, staticValue: string | boolean | null) { + onSelectionChanged({ + ...fieldMappingItem, + staticValue, + }); + } + + function handleFieldSelectionChange(field: FieldWithRelatedEntities) { + if (field.name !== fieldMappingItem.targetField) { + onSelectionChanged({ + ...fieldMappingItem, + staticValue: null, + targetField: field.name, + mappedToLookup: false, + targetLookupField: undefined, + fieldMetadata: field, + }); + } + } + + return ( + + + {editableField && ( + + )} + + + {fieldMappingItem.targetField && ( + + )} + + + handleFieldSelectionChange(item.meta)} + /> + + +
    + +
    + + + ); +}; + +export default LoadRecordsFieldMappingStaticRow; diff --git a/apps/jetstream/src/app/components/load-records/components/LoadRecordsLoadTypeButtons.tsx b/apps/jetstream/src/app/components/load-records/components/LoadRecordsLoadTypeButtons.tsx index 5cf4ee96a..1a4240763 100644 --- a/apps/jetstream/src/app/components/load-records/components/LoadRecordsLoadTypeButtons.tsx +++ b/apps/jetstream/src/app/components/load-records/components/LoadRecordsLoadTypeButtons.tsx @@ -20,7 +20,7 @@ export const LoadRecordsLoadTypeButtons: FunctionComponent { - const [externalIdField, setExternalIdFields] = useState[]>(() => + const [externalIdFieldOptions, setExternalIdFieldOptions] = useState[]>(() => externalIdFields.map((field) => ({ id: field.name, label: field.label, @@ -42,7 +42,7 @@ export const LoadRecordsLoadTypeButtons: FunctionComponent ({ id: field.name, label: field.label, @@ -114,7 +114,7 @@ export const LoadRecordsLoadTypeButtons: FunctionComponent handleExternalIdChange(item.id)} /> diff --git a/apps/jetstream/src/app/components/load-records/components/LoadRecordsRefreshCachePopover.tsx b/apps/jetstream/src/app/components/load-records/components/LoadRecordsRefreshCachePopover.tsx index e1f0e6c4a..8496c191d 100644 --- a/apps/jetstream/src/app/components/load-records/components/LoadRecordsRefreshCachePopover.tsx +++ b/apps/jetstream/src/app/components/load-records/components/LoadRecordsRefreshCachePopover.tsx @@ -2,8 +2,8 @@ import { css } from '@emotion/react'; import { SalesforceOrgUi } from '@jetstream/types'; import { Icon, Popover, PopoverRef, SalesforceLogin, Spinner } from '@jetstream/ui'; import { FunctionComponent, useRef } from 'react'; -import { useRecoilState } from 'recoil'; -import { applicationCookieState } from '../../../app-state'; +import { useRecoilValue } from 'recoil'; +import { applicationCookieState, selectSkipFrontdoorAuth } from '../../../app-state'; export interface LoadRecordsRefreshCachePopoverProps { org: SalesforceOrgUi; @@ -19,7 +19,8 @@ export const LoadRecordsRefreshCachePopover: FunctionComponent { const popoverRef = useRef(null); - const [{ serverUrl }] = useRecoilState(applicationCookieState); + const { serverUrl } = useRecoilValue(applicationCookieState); + const skipFrontDoorAuth = useRecoilValue(selectSkipFrontdoorAuth); function handleReload() { popoverRef.current?.close(); @@ -47,6 +48,7 @@ export const LoadRecordsRefreshCachePopover: FunctionComponent([STATUSES.PREPARING, STATUSES.PROCESSING, STATUSES.ABORTING]); export interface LoadRecordsBatchApiResultsProps { @@ -76,10 +77,9 @@ export const LoadRecordsBatchApiResults: FunctionComponent { const isMounted = useRef(true); + const isAborted = useRef(false); const { trackEvent } = useAmplitude(); const rollbar = useRollbar(); - // used to ensure that data in the onworker callback gets a reference to the results - const [loadWorker] = useState(() => getLoadWorker()); const processingStatusRef = useRef<{ success: number; failure: number }>({ success: 0, failure: 0 }); const [preparedData, setPreparedData] = useState(); const [prepareDataProgress, setPrepareDataProgress] = useState(0); @@ -107,12 +107,12 @@ export const LoadRecordsBatchApiResults: FunctionComponent { - if (loadWorker) { + const doPrepareData = useCallback(async () => { + try { setStatus(STATUSES.PREPARING); setProcessingStartTime(convertDateToLocale(new Date(), { timeStyle: 'medium' })); setFatalError(null); - const data: PrepareDataPayload = { + const prepareDataPayload: PrepareDataPayload = { org: selectedOrg, data: inputFileData, fieldMapping, @@ -121,16 +121,73 @@ export const LoadRecordsBatchApiResults: FunctionComponent { + setPrepareDataProgress(progress || 0); + }); + + if (isAborted.current) { + throw new Error('Aborted'); + } + + const dateString = convertDateToLocale(new Date(), { timeStyle: 'medium' }); + + if (!preparedDataResponse?.data.length) { + if (preparedDataResponse?.queryErrors?.length) { + setFatalError(preparedDataResponse.queryErrors.join('\n')); + } + + setStatus(STATUSES.ERROR); + setPreparedData(preparedData); + setProcessingEndTime(dateString); + setStartTime(dateString); + setEndTime(dateString); + onFinish({ success: 0, failure: inputFileData.length }); + notifyUser(`Your ${loadType.toLowerCase()} data load failed`, { + body: `❌ Pre-processing records failed.`, + tag: 'load-records', + }); + rollbar.error('Error preparing batch api data', { + message: 'Pre-processing failed', + queryErrors: preparedData?.queryErrors, + errors: preparedDataResponse.errors?.flatMap((error) => error.errors) || [], + }); + } else { + setStatus(STATUSES.PROCESSING); + setPreparedData(preparedDataResponse); + setStartTime(dateString); + setProcessingEndTime(dateString); + + return preparedDataResponse; + } + } catch (ex) { + logger.error('ERROR', ex); + setStatus(STATUSES.ERROR); + setFatalError(ex.message); + onFinish({ success: 0, failure: inputFileData.length }); + notifyUser(`Your ${loadType.toLowerCase()} data load failed`, { + body: `❌ ${ex.message}`, + tag: 'load-records', + }); + rollbar.error('Error preparing batch api data', { message: ex.message, stack: ex.stack }); + return; } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - useEffect(() => { - if (preparedData && preparedData.data.length) { - const data: LoadDataPayload = { + const loadData = useCallback(async () => { + isAborted.current = false; + + const preparedDataResponse = await doPrepareData(); + + if (!preparedDataResponse) { + return; + } + + try { + const loadDataPayload: LoadDataPayload = { org: selectedOrg, - data: preparedData.data, + data: preparedDataResponse.data, sObject: selectedSObject, apiMode, type: loadType, @@ -141,10 +198,38 @@ export const LoadRecordsBatchApiResults: FunctionComponent field.isBinaryBodyField)?.targetField, zipData: inputZipFileData, }; - loadWorker.postMessage({ name: 'loadData', data }); + + await loadBatchApiData( + loadDataPayload, + (records) => { + setProcessedRecords((previousProcessedRecords) => previousProcessedRecords.concat(records || [])); + }, + () => isAborted.current + ); + + const dateString = convertDateToLocale(new Date(), { timeStyle: 'medium' }); + + setStatus(STATUSES.FINISHED); + onFinish({ success: processingStatusRef.current.success, failure: processingStatusRef.current.failure }); + setEndTime(dateString); + } catch (ex) { + const dateString = convertDateToLocale(new Date(), { timeStyle: 'medium' }); + logger.error('ERROR', ex); + setStatus(STATUSES.ERROR); + onFinish({ success: 0, failure: inputFileData.length }); + setEndTime(dateString); + notifyUser(`Your ${loadType.toLowerCase()} data load failed`, { + body: `❌ ${ex.message}`, + tag: 'load-records', + }); + rollbar.error('Error loading batches', { message: ex.message, stack: ex.stack }); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [preparedData]); + }, []); + + useEffect(() => { + loadData(); + }, [loadData]); useEffect(() => { if (Array.isArray(processedRecords) && processedRecords.length > 0) { @@ -176,93 +261,6 @@ export const LoadRecordsBatchApiResults: FunctionComponent { - if (loadWorker) { - loadWorker.onmessage = (event: MessageEvent) => { - if (!isMounted.current) { - return; - } - const payload: WorkerMessage< - 'prepareData' | 'prepareDataProgress' | 'loadDataStatus' | 'loadData', - { preparedData?: PrepareDataResponse; progress?: number; records?: RecordResultWithRecord[] } - > = event.data; - logger.log('[LOAD DATA]', payload.name, { payload }); - const dateString = convertDateToLocale(new Date(), { timeStyle: 'medium' }); - switch (payload.name) { - case 'prepareData': { - if (payload.error) { - logger.error('ERROR', payload.error); - setStatus(STATUSES.ERROR); - setFatalError(payload.error.message); - onFinish({ success: 0, failure: inputFileData.length }); - notifyUser(`Your ${loadType.toLowerCase()} data load failed`, { - body: `❌ ${payload.error?.message || payload.error}`, - tag: 'load-records', - }); - rollbar.error('Error preparing batch api data', { message: payload.error.message, stack: payload.error.stack }); - } else if (!payload.data.preparedData?.data.length) { - if (payload.data.preparedData?.queryErrors?.length) { - setFatalError(payload.data.preparedData.queryErrors.join('\n')); - } else if (payload.error) { - setFatalError(payload.error.message); - } - setStatus(STATUSES.ERROR); - setPreparedData(payload.data.preparedData); - setProcessingEndTime(dateString); - setStartTime(dateString); - setEndTime(dateString); - onFinish({ success: 0, failure: inputFileData.length }); - notifyUser(`Your ${loadType.toLowerCase()} data load failed`, { - body: `❌ Pre-processing records failed.`, - tag: 'load-records', - }); - rollbar.error('Error preparing batch api data', { - queryErrors: payload.data.preparedData?.queryErrors, - message: payload.error?.message, - stack: payload.error?.stack, - }); - } else { - setStatus(STATUSES.PROCESSING); - setPreparedData(payload.data.preparedData); - setStartTime(dateString); - setProcessingEndTime(dateString); - } - break; - } - case 'prepareDataProgress': { - setPrepareDataProgress(payload.data.progress || 0); - break; - } - case 'loadDataStatus': { - setProcessedRecords((previousProcessedRecords) => previousProcessedRecords.concat(payload.data.records || [])); - break; - } - case 'loadData': { - if (payload.error) { - logger.error('ERROR', payload.error); - setStatus(STATUSES.ERROR); - onFinish({ success: 0, failure: inputFileData.length }); - setEndTime(dateString); - notifyUser(`Your ${loadType.toLowerCase()} data load failed`, { - body: `❌ ${payload.error?.message || payload.error}`, - tag: 'load-records', - }); - rollbar.error('Error loading batches', { message: payload.error.message, stack: payload.error.stack }); - } else { - setStatus(STATUSES.FINISHED); - onFinish({ success: processingStatusRef.current.success, failure: processingStatusRef.current.failure }); - setEndTime(dateString); - } - break; - } - default: - break; - } - }; - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [loadWorker, processingStatusRef.current]); - function handleDownloadRecords(type: 'results' | 'failures') { // eslint-disable-next-line @typescript-eslint/no-explicit-any const combinedResults: any[] = []; @@ -274,7 +272,10 @@ export const LoadRecordsBatchApiResults: FunctionComponent `${error.statusCode}: ${error.message}`).join('\n') : '', + _errors: + record.success === false + ? record.errors.map((error) => `${error.statusCode}: ${decodeHtmlEntity(error.message)}`).join('\n') + : '', ...flattenRecord(record.record, fields), }); } @@ -300,7 +301,10 @@ export const LoadRecordsBatchApiResults: FunctionComponent `${error.statusCode}: ${error.message}`).join('\n') : '', + _errors: + record.success === false + ? record.errors.map((error) => `${error.statusCode}: ${decodeHtmlEntity(error.message)}`).join('\n') + : '', ...flattenRecord(record.record, fields), }); } @@ -356,7 +360,7 @@ export const LoadRecordsBatchApiResults: FunctionComponent )} - +

    @@ -405,7 +409,7 @@ export const LoadRecordsBatchApiResults: FunctionComponent - +

    )} diff --git a/apps/jetstream/src/app/components/load-records/components/load-results/LoadRecordsBulkApiResults.tsx b/apps/jetstream/src/app/components/load-records/components/load-results/LoadRecordsBulkApiResults.tsx index eb6a10de6..f4891cb55 100644 --- a/apps/jetstream/src/app/components/load-records/components/load-results/LoadRecordsBulkApiResults.tsx +++ b/apps/jetstream/src/app/components/load-records/components/load-results/LoadRecordsBulkApiResults.tsx @@ -3,7 +3,7 @@ import { logger } from '@jetstream/shared/client-logger'; import { ANALYTICS_KEYS } from '@jetstream/shared/constants'; import { bulkApiAbortJob, bulkApiGetJob, bulkApiGetRecords } from '@jetstream/shared/data'; import { checkIfBulkApiJobIsDone, convertDateToLocale, useBrowserNotifications, useRollbar } from '@jetstream/shared/ui-utils'; -import { getSuccessOrFailureChar, pluralizeFromNumber } from '@jetstream/shared/utils'; +import { decodeHtmlEntity, getSuccessOrFailureChar, pluralizeFromNumber } from '@jetstream/shared/utils'; import { BulkJobBatchInfo, BulkJobResultRecord, @@ -12,13 +12,11 @@ import { MapOf, Maybe, SalesforceOrgUi, - WorkerMessage, } from '@jetstream/types'; -import { FileDownloadModal, Grid, ProgressRing, SalesforceLogin, Spinner, Tooltip } from '@jetstream/ui'; -import { FunctionComponent, useEffect, useRef, useState } from 'react'; -import { useRecoilState } from 'recoil'; -import { applicationCookieState } from '../../../../app-state'; -import { fireToast } from '../../../core/AppToast'; +import { FileDownloadModal, Grid, ProgressRing, SalesforceLogin, Spinner, Tooltip, fireToast } from '@jetstream/ui'; +import { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'; +import { useRecoilValue } from 'recoil'; +import { applicationCookieState, selectSkipFrontdoorAuth } from '../../../../app-state'; import { useAmplitude } from '../../../core/analytics'; import * as fromJetstreamEvents from '../../../core/jetstream-events'; import LoadRecordsBulkApiResultsTable from '../../../shared/load-records-results/LoadRecordsBulkApiResultsTable'; @@ -33,8 +31,8 @@ import { PrepareDataResponse, ViewModalData, } from '../../load-records-types'; -import { LoadRecordsBatchError, getFieldHeaderFromMapping } from '../../utils/load-records-utils'; -import { getLoadWorker } from '../../utils/load-records-worker'; +import { loadBulkApiData, prepareData } from '../../utils/load-records-process'; +import { getFieldHeaderFromMapping } from '../../utils/load-records-utils'; import LoadRecordsResultsModal from './LoadRecordsResultsModal'; type Status = 'Preparing Data' | 'Uploading Data' | 'Processing Data' | 'Aborting' | 'Finished' | 'Error'; @@ -93,12 +91,13 @@ export const LoadRecordsBulkApiResults: FunctionComponent { const isMounted = useRef(true); + const isAborted = useRef(false); const { trackEvent } = useAmplitude(); const rollbar = useRollbar(); - const [{ serverUrl, google_apiKey, google_appId, google_clientId }] = useRecoilState(applicationCookieState); + const { serverUrl, google_apiKey, google_appId, google_clientId } = useRecoilValue(applicationCookieState); + const skipFrontDoorAuth = useRecoilValue(selectSkipFrontdoorAuth); const [preparedData, setPreparedData] = useState(); const [prepareDataProgress, setPrepareDataProgress] = useState(0); - const [loadWorker] = useState(() => getLoadWorker()); const [status, setStatus] = useState(STATUSES.PREPARING); const [fatalError, setFatalError] = useState>(null); const [downloadError, setDownloadError] = useState>(null); @@ -125,12 +124,6 @@ export const LoadRecordsBulkApiResults: FunctionComponent { - if (loadWorker) { - loadWorker.postMessage({ name: 'init', isElectron: window.electron?.isElectron }); - } - }, [loadWorker]); - useEffect(() => { if (batchSummary && batchSummary.batchSummary) { const batchSummariesWithId = batchSummary.batchSummary.filter((batch) => batch.id); @@ -147,45 +140,6 @@ export const LoadRecordsBulkApiResults: FunctionComponent { - if (loadWorker) { - setStatus(STATUSES.PREPARING); - setProcessingStartTime(convertDateToLocale(new Date(), { timeStyle: 'medium' })); - setFatalError(null); - const data: PrepareDataPayload = { - org: selectedOrg, - data: inputFileData, - // zipData: inputZipFileData, - fieldMapping, - sObject: selectedSObject, - insertNulls, - dateFormat, - apiMode, - }; - loadWorker.postMessage({ name: 'prepareData', data }); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [loadWorker]); - - useEffect(() => { - if (preparedData && preparedData.data.length) { - const data: LoadDataPayload = { - org: selectedOrg, - data: preparedData.data, - zipData: inputZipFileData, - sObject: selectedSObject, - apiMode, - type: loadType, - batchSize, - assignmentRuleId, - serialMode, - externalId, - }; - loadWorker.postMessage({ name: 'loadData', data }); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [preparedData]); - /** * When jobInfo is modified, check to see if everything is done * If not done and status is processing, then continue polling @@ -233,139 +187,163 @@ export const LoadRecordsBulkApiResults: FunctionComponent { - if (loadWorker) { - loadWorker.onmessage = (event: MessageEvent) => { - if (!isMounted.current) { - return; + const doPrepareData = useCallback(async () => { + try { + setStatus(STATUSES.PREPARING); + setProcessingStartTime(convertDateToLocale(new Date(), { timeStyle: 'medium' })); + setFatalError(null); + const prepareDataPayload: PrepareDataPayload = { + org: selectedOrg, + data: inputFileData, + fieldMapping, + sObject: selectedSObject, + insertNulls, + dateFormat, + apiMode, + }; + + const preparedDataResponse = await prepareData(prepareDataPayload, (progress) => { + setPrepareDataProgress(progress || 0); + }); + + if (isAborted.current) { + throw new Error('Aborted'); + } + + const dateString = convertDateToLocale(new Date(), { timeStyle: 'medium' }); + + if (!preparedDataResponse?.data.length) { + if (preparedDataResponse?.queryErrors?.length) { + setFatalError(preparedDataResponse.queryErrors.join('\n')); } - const payload: WorkerMessage< - 'prepareData' | 'prepareDataProgress' | 'loadDataStatus' | 'loadData', - { - preparedData?: PrepareDataResponse; - jobInfo?: BulkJobWithBatches; - progress?: number; - resultsSummary?: LoadDataBulkApiStatusPayload; - }, - Error | LoadRecordsBatchError - > = event.data; - logger.log('[LOAD DATA]', payload.name, { payload }); - const dateString = convertDateToLocale(new Date(), { timeStyle: 'medium' }); - switch (payload.name) { - case 'prepareData': { - if (payload.error) { - logger.error('ERROR', payload.error); - setStatus(STATUSES.ERROR); - setFatalError(payload.error.message); - onFinish({ success: 0, failure: inputFileData.length }); - notifyUser(`Your ${loadType.toLowerCase()} data load failed`, { - body: `❌ ${payload.error.message}`, - tag: 'load-records', - }); - rollbar.error('Error preparing bulk api data', { message: payload.error.message, stack: payload.error.stack }); - } else if (!payload.data.preparedData?.data.length) { - if (payload.data.preparedData?.queryErrors?.length) { - setFatalError(payload.data.preparedData.queryErrors.join('\n')); - } else if (payload.error) { - setFatalError((payload.error as any)?.message || 'An unknown error has occurred'); - } - // processing failed on every record - setStatus(STATUSES.ERROR); - setPreparedData(payload.data.preparedData); - setProcessingEndTime(dateString); - // mock response to ensure results table is visible - setJobInfo({ - concurrencyMode: serialMode ? 'Serial' : 'Parallel', - contentType: 'CSV', - createdById: null, - createdDate: null, - id: null, - object: selectedSObject, - operation: loadType, - state: 'Failed', - systemModstamp: null, - apexProcessingTime: 0, - apiActiveProcessingTime: 0, - apiVersion: 0, - numberBatchesCompleted: 0, - numberBatchesFailed: 0, - numberBatchesInProgress: 0, - numberBatchesQueued: 0, - numberBatchesTotal: 0, - numberRecordsFailed: 0, - numberRecordsProcessed: 0, - numberRetries: 0, - totalProcessingTime: 0, - batches: [], - }); - onFinish({ success: 0, failure: inputFileData.length }); - notifyUser(`Your ${loadType.toLowerCase()} data load failed`, { - body: `❌ Pre-processing records failed.`, - tag: 'load-records', - }); - rollbar.error('Error preparing bulk api data', { - queryErrors: payload.data.preparedData?.queryErrors, - message: (payload.error as any)?.message, - stack: (payload.error as any)?.stack, - }); - } else { - setStatus(STATUSES.UPLOADING); - setPreparedData(payload.data.preparedData); - setProcessingEndTime(dateString); - } - break; - } - case 'prepareDataProgress': { - setPrepareDataProgress(payload.data.progress || 0); - break; - } - case 'loadDataStatus': { - setBatchSummary(payload.data.resultsSummary); - if (Array.isArray(payload.data.resultsSummary?.jobInfo.batches) && payload.data.resultsSummary?.jobInfo.batches.length) { - setJobInfo(payload.data.resultsSummary.jobInfo); - } - break; - } - case 'loadData': { - if (payload.error) { - logger.error('ERROR', payload.error); - setFatalError(payload.error.message); - if (payload.data?.jobInfo && payload.data.jobInfo.batches.length) { - setJobInfo(payload.data.jobInfo); - setStatus(STATUSES.PROCESSING); - } else { - setStatus(STATUSES.ERROR); - onFinish({ success: 0, failure: inputFileData.length }); - notifyUser(`Your data load failed`, { - body: `❌ ${payload.error?.message || payload.error}`, - tag: 'load-records', - }); - } - if (payload.error instanceof LoadRecordsBatchError) { - rollbar.error('Error loading batches', { - message: payload.error.message, - stack: payload.error.stack, - specificErrors: payload.error.additionalErrors.map((error) => ({ - message: error.message, - stack: error.stack, - })), - }); - } else { - rollbar.error('Error loading batches', { message: payload.error.message, stack: payload.error.stack }); - } - } else { - setJobInfo(payload.data.jobInfo); - setStatus(STATUSES.PROCESSING); - } - break; + // processing failed on every record + setStatus(STATUSES.ERROR); + setPreparedData(preparedDataResponse); + setProcessingEndTime(dateString); + // mock response to ensure results table is visible + setJobInfo({ + concurrencyMode: serialMode ? 'Serial' : 'Parallel', + contentType: 'CSV', + createdById: null, + createdDate: null, + id: null, + object: selectedSObject, + operation: loadType, + state: 'Failed', + systemModstamp: null, + apexProcessingTime: 0, + apiActiveProcessingTime: 0, + apiVersion: 0, + numberBatchesCompleted: 0, + numberBatchesFailed: 0, + numberBatchesInProgress: 0, + numberBatchesQueued: 0, + numberBatchesTotal: 0, + numberRecordsFailed: 0, + numberRecordsProcessed: 0, + numberRetries: 0, + totalProcessingTime: 0, + batches: [], + }); + onFinish({ success: 0, failure: inputFileData.length }); + notifyUser(`Your ${loadType.toLowerCase()} data load failed`, { + body: `❌ Pre-processing records failed.`, + tag: 'load-records', + }); + rollbar.error('Error preparing bulk api data', { queryErrors: preparedDataResponse?.queryErrors }); + } else { + setStatus(STATUSES.UPLOADING); + setPreparedData(preparedDataResponse); + setProcessingEndTime(dateString); + + return preparedDataResponse; + } + } catch (ex) { + logger.error('ERROR', ex); + setStatus(STATUSES.ERROR); + setFatalError(ex.message); + onFinish({ success: 0, failure: inputFileData.length }); + notifyUser(`Your ${loadType.toLowerCase()} data load failed`, { + body: `❌ ${ex.message}`, + tag: 'load-records', + }); + rollbar.error('Error preparing bulk api data', { message: ex.message, stack: ex.stack }); + return; + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const loadData = useCallback(async () => { + isAborted.current = false; + + const preparedDataResponse = await doPrepareData(); + + if (!preparedDataResponse) { + return; + } + + try { + const loadDataPayload: LoadDataPayload = { + org: selectedOrg, + data: preparedDataResponse.data, + zipData: inputZipFileData, + sObject: selectedSObject, + apiMode, + type: loadType, + batchSize, + assignmentRuleId, + serialMode, + externalId, + }; + + const { loadError, jobInfo } = await loadBulkApiData( + loadDataPayload, + (resultsSummary) => { + setBatchSummary(resultsSummary); + if (Array.isArray(resultsSummary?.jobInfo.batches) && resultsSummary?.jobInfo.batches.length) { + setJobInfo(resultsSummary.jobInfo); } - default: - break; + }, + () => isAborted.current + ); + + if (loadError) { + logger.error('ERROR', loadError); + setFatalError(loadError.message); + if (jobInfo && jobInfo.batches.length) { + setJobInfo(jobInfo); + setStatus(STATUSES.PROCESSING); + } else { + setStatus(STATUSES.ERROR); + onFinish({ success: 0, failure: inputFileData.length }); + notifyUser(`Your data load failed`, { + body: `❌ ${loadError.message}`, + tag: 'load-records', + }); } - }; + rollbar.error('Error loading batches', { + message: loadError.message, + stack: loadError.stack, + specificErrors: loadError.additionalErrors.map((error) => ({ + message: error.message, + stack: error.stack, + })), + }); + } else { + setJobInfo(jobInfo); + setStatus(STATUSES.PROCESSING); + } + } catch (ex) { + logger.error('ERROR', ex); + setFatalError(ex.message); + return; } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [loadWorker]); + }, []); + + useEffect(() => { + loadData(); + }, [loadData]); function getUploadingText() { if ( @@ -408,7 +386,7 @@ export const LoadRecordsBulkApiResults: FunctionComponent )} - +

    @@ -535,7 +513,7 @@ export const LoadRecordsBulkApiResults: FunctionComponent - +

    )} @@ -555,6 +533,7 @@ export const LoadRecordsBulkApiResults: FunctionComponent diff --git a/apps/jetstream/src/app/components/load-records/components/load-results/LoadRecordsResults.tsx b/apps/jetstream/src/app/components/load-records/components/load-results/LoadRecordsResults.tsx index 7b3c85241..0675d1174 100644 --- a/apps/jetstream/src/app/components/load-records/components/load-results/LoadRecordsResults.tsx +++ b/apps/jetstream/src/app/components/load-records/components/load-results/LoadRecordsResults.tsx @@ -38,7 +38,7 @@ export const LoadRecordsResults: FunctionComponent = ({ onFinish, }) => { return ( -
    +
    {apiMode === 'BULK' && ( ) => (type === 'ROW' && row?._errors ? 75 : 25); +const getRowHeight = (row: any) => (row?._errors ? 75 : 25); export interface LoadRecordsResultsModalProps { type: 'results' | 'failures'; @@ -47,36 +46,37 @@ export const LoadRecordsResultsModal: FunctionComponent { if (header) { setColumns( - header.map((item) => ({ - ...setColumnFromType(item, 'text'), - name: item, - key: item, - field: item, - resizable: true, - width: COL_WIDTH_MAP[item], - formatter: - item === '_errors' - ? ({ row }) => ( -

    - {row._errors && ( - - { + const baseColumn = setColumnFromType(item, getRowTypeFromValue(rows?.[0]?.[item], false)); + return { + ...baseColumn, + name: item, + key: item, + field: item, + resizable: true, + width: COL_WIDTH_MAP[item], + formatter: + item === '_errors' + ? ({ row }) => ( +

    + {row._errors && ( + - - )} - {row?._errors} -

    - ) - : undefined, - })) + )} + {row?._errors} +

    + ) + : baseColumn.renderCell, + }; + }) ); } }, [header]); @@ -116,7 +116,7 @@ export const LoadRecordsResultsModal: FunctionComponent {loading && } - {rows && columns && ( + {Array.isArray(rows) && Array.isArray(columns) && ( ; + field: FieldWithExtendedType; } export interface FieldRelatedEntity { @@ -58,8 +60,12 @@ export interface SavedFieldMapping { [field: string]: Omit; } -export interface FieldMappingItem { +export type FieldMappingItem = FieldMappingItemCsv | FieldMappingItemStatic; + +export interface FieldMappingItemBase { + type: 'CSV' | 'STATIC'; csvField: string; + staticValue?: string | boolean | null; targetField: string | null; mappedToLookup: boolean; selectedReferenceTo?: string; @@ -68,11 +74,28 @@ export interface FieldMappingItem { fieldMetadata: Maybe; relatedFieldMetadata?: FieldRelatedEntity; isDuplicateMappedField?: boolean; + fieldErrorMsg?: string; lookupOptionUseFirstMatch: NonExtIdLookupOption; lookupOptionNullIfNoMatch: boolean; isBinaryBodyField: boolean; } +export interface FieldMappingItemCsv extends FieldMappingItemBase { + type: 'CSV'; + staticValue?: never; +} + +export interface FieldMappingItemStatic extends FieldMappingItemBase { + type: 'STATIC'; + staticValue: string | boolean | null; + mappedToLookup: false; + selectedReferenceTo?: never; + relationshipName?: never; + targetLookupField?: never; + lookupOptionNullIfNoMatch: false; + isBinaryBodyField: false; +} + export interface PrepareDataPayload { org: SalesforceOrgUi; data: any[]; diff --git a/apps/jetstream/src/app/components/load-records/load-records.state.ts b/apps/jetstream/src/app/components/load-records/load-records.state.ts index aafa12f88..87016d251 100644 --- a/apps/jetstream/src/app/components/load-records/load-records.state.ts +++ b/apps/jetstream/src/app/components/load-records/load-records.state.ts @@ -1,15 +1,26 @@ import { logger } from '@jetstream/shared/client-logger'; import { INDEXED_DB } from '@jetstream/shared/constants'; -import { InsertUpdateUpsertDelete, MapOf } from '@jetstream/types'; +import { detectDateFormatForLocale, formatNumber } from '@jetstream/shared/ui-utils'; +import { InsertUpdateUpsertDelete, MapOf, Maybe } from '@jetstream/types'; import type { DescribeGlobalSObjectResult } from 'jsforce'; import localforage from 'localforage'; +import isNumber from 'lodash/isNumber'; import { atom, selector, selectorFamily } from 'recoil'; -import { FieldMapping, LocalOrGoogle, SavedFieldMapping } from './load-records-types'; +import { ApiMode, FieldMapping, LocalOrGoogle, SavedFieldMapping } from './load-records-types'; +import { + BATCH_RECOMMENDED_THRESHOLD, + MAX_API_CALLS, + MAX_BULK, + STATIC_MAPPING_PREFIX, + getLabelWithOptionalRecommended, + getMaxBatchSize, +} from './utils/load-records-utils'; const SUPPORTED_ATTACHMENT_OBJECTS = new Map(); SUPPORTED_ATTACHMENT_OBJECTS.set('Attachment', { bodyField: 'Body' }); SUPPORTED_ATTACHMENT_OBJECTS.set('Document', { bodyField: 'Body' }); SUPPORTED_ATTACHMENT_OBJECTS.set('ContentVersion', { bodyField: 'VersionData' }); +const DATE_FIELDS = new Set(['date', 'datetime']); export interface LoadSavedMappingItem { key: string; // object:createdDate @@ -53,7 +64,9 @@ export const selectSavedFieldMappingState = selectorFamily< ({ get }) => { const savedMappings = Object.values(get(savedFieldMappingState)[sobject] || {}); return savedMappings.filter( - (item) => item.csvFields.every((field) => csvFields.has(field)) && item.sobjectFields.every((field) => objectFields.has(field)) + (item) => + item.csvFields.filter((field) => !field.startsWith(STATIC_MAPPING_PREFIX)).every((field) => csvFields.has(field)) && + item.sobjectFields.every((field) => objectFields.has(field)) ); }, }); @@ -146,3 +159,101 @@ export const isCustomMetadataObject = selector({ return selectedObject && selectedObject.name.endsWith('__mdt'); }, }); + +export const apiModeState = atom({ + key: 'apiModeState', + default: 'BATCH', +}); + +export const batchSizeState = atom>({ + key: 'batchSizeState', + default: MAX_BULK, +}); + +export const insertNullsState = atom({ + key: 'insertNullsState', + default: false, +}); + +export const serialModeState = atom({ + key: 'serialModeState', + default: false, +}); + +export const trialRunState = atom({ + key: 'trialRunState', + default: false, +}); + +export const trialRunSizeState = atom>({ + key: 'trialRunSizeState', + default: 1, +}); + +export const dateFormatState = atom({ + key: 'dateFormatState', + default: detectDateFormatForLocale(), +}); + +export const selectHasDateFieldMapped = selector({ + key: 'load.selectHasDateFieldMapped', + get: ({ get }) => Object.values(get(fieldMappingState)).some((item) => item.fieldMetadata && DATE_FIELDS.has(item.fieldMetadata.type)), +}); + +export const selectBatchSizeError = selector({ + key: 'load.selectBatchSizeError', + get: ({ get }) => { + const batchSize = get(batchSizeState) || 0; + const apiMode = get(apiModeState); + if (!isNumber(batchSize) || batchSize <= 0 || batchSize > getMaxBatchSize(apiMode)) { + return `The batch size must be between 1 and ${formatNumber(getMaxBatchSize(apiMode))}`; + } + return null; + }, +}); + +export const selectBatchApiLimitError = selector({ + key: 'load.selectBatchApiLimitError', + get: ({ get }) => { + const inputFileDataLength = get(inputFileDataState)?.length || 0; + const batchSize = get(batchSizeState) || 1; + if (inputFileDataLength && batchSize && inputFileDataLength / batchSize > MAX_API_CALLS) { + const numApiCalls = Math.round(inputFileDataLength / batchSize); + return ( + `Either your batch size is too low or you are loading in too many records. ` + + `Your configuration would require ${formatNumber(numApiCalls)} calls to Salesforce, which exceeds the limit of ${MAX_API_CALLS}. ` + + `Increase your batch size or reduce the number of records in your file.` + ); + } + return null; + }, +}); + +export const selectTrialRunSizeError = selector({ + key: 'load.selectTrialRunSizeError', + get: ({ get }) => { + const inputLength = get(inputFileDataState)?.length || 1; + const trialRunSize = get(trialRunSizeState) || 1; + if (!isNumber(trialRunSize) || trialRunSize <= 0 || trialRunSize >= inputLength) { + return `Must be between 1 and ${formatNumber(inputLength - 1)}`; + } + return null; + }, +}); + +export const selectBulkApiModeLabel = selector({ + key: 'load.selectBulkApiModeLabel', + get: ({ get }) => { + const inputFileDataLength = get(inputFileDataState)?.length || 0; + return getLabelWithOptionalRecommended('Bulk API', inputFileDataLength > BATCH_RECOMMENDED_THRESHOLD, false); + }, +}); + +export const selectBatchApiModeLabel = selector({ + key: 'load.selectBatchApiModeLabel', + get: ({ get }) => { + const inputFileDataLength = get(inputFileDataState)?.length || 0; + const hasZipAttachment = !!get(inputZipFileDataState); + return getLabelWithOptionalRecommended('Batch API', inputFileDataLength <= BATCH_RECOMMENDED_THRESHOLD, hasZipAttachment); + }, +}); diff --git a/apps/jetstream/src/app/components/load-records/steps/FieldMapping.tsx b/apps/jetstream/src/app/components/load-records/steps/FieldMapping.tsx index b645dcab2..fd4cbf39f 100644 --- a/apps/jetstream/src/app/components/load-records/steps/FieldMapping.tsx +++ b/apps/jetstream/src/app/components/load-records/steps/FieldMapping.tsx @@ -4,19 +4,28 @@ import { ANALYTICS_KEYS } from '@jetstream/shared/constants'; import { useDebounce, useNonInitialEffect } from '@jetstream/shared/ui-utils'; import { multiWordStringFilter } from '@jetstream/shared/utils'; import { InsertUpdateUpsertDelete, Maybe, SalesforceOrgUi } from '@jetstream/types'; -import { Alert, ButtonGroupContainer, DropDown, Grid, GridCol, Icon, SearchInput } from '@jetstream/ui'; +import { Alert, ButtonGroupContainer, DropDown, Grid, GridCol, Icon, SearchInput, Tooltip } from '@jetstream/ui'; import classNames from 'classnames'; import { memo, useEffect, useRef, useState } from 'react'; import { useAmplitude } from '../../core/analytics'; import LoadRecordsFieldMappingRow from '../components/LoadRecordsFieldMappingRow'; +import LoadRecordsFieldMappingStaticRow from '../components/LoadRecordsFieldMappingStaticRow'; import LoadRecordsRefreshCachePopover from '../components/LoadRecordsRefreshCachePopover'; import { LoadMappingPopover } from '../components/load-mapping-storage/LoadMappingPopover'; import SaveMappingPopover from '../components/load-mapping-storage/SaveMappingPopover'; -import { FieldMapping, FieldMappingItem, FieldWithRelatedEntities } from '../load-records-types'; +import { + FieldMapping, + FieldMappingItem, + FieldMappingItemCsv, + FieldMappingItemStatic, + FieldWithRelatedEntities, +} from '../load-records-types'; import { LoadSavedMappingItem } from '../load-records.state'; import { autoMapFields, + checkFieldsForMappingError, checkForDuplicateFieldMappings, + initStaticFieldMappingItem, loadFieldMappingFromSavedMapping, resetFieldMapping, } from '../utils/load-records-utils'; @@ -66,6 +75,11 @@ export const LoadRecordsFieldMapping = memo( const [csvFields, setCsvFields] = useState(() => new Set(inputHeader)); const [objectFields, setObjectFields] = useState(() => new Set(fields.map((field) => field.name))); const [visibleHeaders, setVisibleHeaders] = useState(inputHeader); + const [staticRowHeaders, setStaticRowHeaders] = useState(() => + Object.values(fieldMappingInit) + .filter((item) => item.type === 'STATIC') + .map((item) => item.csvField) + ); const [activeRowIndex, setActiveRowIndex] = useState(0); const [activeRow, setActiveRow] = useState(() => fileData[activeRowIndex]); // hack to force child re-render when fields are re-mapped @@ -116,12 +130,12 @@ export const LoadRecordsFieldMapping = memo( setWarningMessage('Custom Metadata Objects must have Label and DeveloperName mapped.'); return; } else if (!isCustomMetadataObject && !externalIdMapped) { - setWarningMessage(`Upsert requires the ExternalId filed ${externalId} to be mapped.`); + setWarningMessage(`Upsert requires the ExternalId field ${externalId} to be mapped.`); return; } } setWarningMessage(null); - }, [externalId, fieldMapping, loadType]); + }, [externalId, fieldMapping, isCustomMetadataObject, loadType]); useNonInitialEffect(() => { let tempVisibleHeaders = inputHeader; @@ -145,17 +159,21 @@ export const LoadRecordsFieldMapping = memo( * */ function handleFieldMappingChange(csvField: string, fieldMappingItem: FieldMappingItem) { - setFieldMapping((fieldMapping) => checkForDuplicateFieldMappings({ ...fieldMapping, [csvField]: fieldMappingItem })); + setFieldMapping((fieldMapping) => + checkFieldsForMappingError({ ...fieldMapping, [csvField]: fieldMappingItem }, loadType, externalId) + ); } function handleAction(id: DropDownAction) { switch (id) { case MAPPING_CLEAR: + setStaticRowHeaders([]); setFieldMapping(resetFieldMapping(inputHeader)); trackEvent(ANALYTICS_KEYS.load_MappingAutomationChanged, { action: id }); break; case MAPPING_RESET: - setFieldMapping(autoMapFields(inputHeader, fields, binaryAttachmentBodyField)); + setStaticRowHeaders([]); + setFieldMapping(autoMapFields(inputHeader, fields, binaryAttachmentBodyField, loadType, externalId)); setFilter(FILTER_ALL); trackEvent(ANALYTICS_KEYS.load_MappingAutomationChanged, { action: id }); break; @@ -172,7 +190,13 @@ export const LoadRecordsFieldMapping = memo( } function handleLoadMapping(savedMapping: LoadSavedMappingItem) { - setFieldMapping(loadFieldMappingFromSavedMapping(savedMapping, inputHeader, fields, binaryAttachmentBodyField)); + const newMapping = loadFieldMappingFromSavedMapping(savedMapping, inputHeader, fields, binaryAttachmentBodyField); + setFieldMapping(newMapping); + setStaticRowHeaders( + Object.values(newMapping) + .filter((item) => item.type === 'STATIC') + .map((item) => item.csvField) + ); trackEvent(ANALYTICS_KEYS.load_SavedMappingLoaded); setKeyPrefix(new Date().getTime()); } @@ -197,8 +221,28 @@ export const LoadRecordsFieldMapping = memo( } } + function handleAddRow() { + const fieldMappingItem = initStaticFieldMappingItem(); + setFieldMapping((fieldMapping) => ({ ...fieldMapping, [fieldMappingItem.csvField]: fieldMappingItem })); + setStaticRowHeaders((prevValue) => [...prevValue, fieldMappingItem.csvField]); + } + + function handleRemoveRow(csvField: string) { + setFieldMapping((fieldMapping) => { + const clonedMapping = { ...fieldMapping }; + delete clonedMapping[csvField]; + return checkForDuplicateFieldMappings(clonedMapping); + }); + setStaticRowHeaders((prevValue) => prevValue.filter((value) => value !== csvField)); + } + return ( - + {warningMessage && ( @@ -227,6 +271,7 @@ export const LoadRecordsFieldMapping = memo( scope="col" css={css` width: 200px; + max-width: 200px; `} > @@ -304,17 +349,32 @@ export const LoadRecordsFieldMapping = memo( {visibleHeaders.map((header, i) => ( ))} + {staticRowHeaders.map((header, i) => ( + handleFieldMappingChange(header, value)} + onRemoveRow={() => handleRemoveRow(header)} + /> + ))} + + + ); diff --git a/apps/jetstream/src/app/components/load-records/steps/PerformLoad.tsx b/apps/jetstream/src/app/components/load-records/steps/PerformLoad.tsx index 1ba985ae4..b4b3fcf5f 100644 --- a/apps/jetstream/src/app/components/load-records/steps/PerformLoad.tsx +++ b/apps/jetstream/src/app/components/load-records/steps/PerformLoad.tsx @@ -1,58 +1,28 @@ import { ANALYTICS_KEYS, DATE_FORMATS, TITLES } from '@jetstream/shared/constants'; -import { formatNumber } from '@jetstream/shared/ui-utils'; +import { formatNumber, useNonInitialEffect } from '@jetstream/shared/ui-utils'; +import { pluralizeIfMultiple } from '@jetstream/shared/utils'; import { InsertUpdateUpsertDelete, Maybe, SalesforceOrgUi, SalesforceOrgUiType } from '@jetstream/types'; -import { Badge, Checkbox, ConfirmationModalPromise, Input, Radio, RadioGroup, Select } from '@jetstream/ui'; -import isNumber from 'lodash/isNumber'; +import { Badge, Checkbox, ConfirmationModalPromise, Grid, Input, Radio, RadioButton, RadioGroup } from '@jetstream/ui'; import startCase from 'lodash/startCase'; -import { ChangeEvent, FunctionComponent, useEffect, useState } from 'react'; -import { useAmplitude } from '../../core/analytics'; +import { ChangeEvent, FunctionComponent, useState } from 'react'; +import { useRecoilState, useRecoilValue } from 'recoil'; import ConfirmPageChange from '../../core/ConfirmPageChange'; -import LoadRecordsResults from '../components/load-results/LoadRecordsResults'; +import { useAmplitude } from '../../core/analytics'; import LoadRecordsAssignmentRules from '../components/LoadRecordsAssignmentRules'; import LoadRecordsDuplicateWarning from '../components/LoadRecordsDuplicateWarning'; -import { ApiMode, FieldMapping } from '../load-records-types'; - -const MAX_BULK = 10000; -const MAX_BATCH = 200; -const MAX_API_CALLS = 250; -const BATCH_RECOMMENDED_THRESHOLD = 2000; - -function getMaxBatchSize(apiMode: ApiMode): number { - if (apiMode === 'BATCH') { - return MAX_BATCH; - } else { - return MAX_BULK; - } -} - -function getLabelWithOptionalRecommended(label: string, recommended: boolean, required: boolean): string | JSX.Element { - if (!recommended && !required) { - return label; - } - if (required) { - return ( - - {label} (Required based on the load configuration) - - ); - } - return ( - - {label} (Recommended based on the number of impacted records) - - ); -} - -function getRecommendedApiMode(numRecords: number, hasBinaryAttachment: boolean): ApiMode { - return !hasBinaryAttachment && numRecords > BATCH_RECOMMENDED_THRESHOLD ? 'BULK' : 'BATCH'; -} +import LoadRecordsResults from '../components/load-results/LoadRecordsResults'; +import { FieldMapping } from '../load-records-types'; +import * as loadRecordsState from '../load-records.state'; +import { getMaxBatchSize } from '../utils/load-records-utils'; -function getBatchSizeExceededError(numApiCalls: number): string { - return ( - `Either your batch size is too low or you are loading in too many records. ` + - `Your configuration would require ${formatNumber(numApiCalls)} calls to Salesforce, which exceeds the limit of ${MAX_API_CALLS}. ` + - `Increase your batch size or reduce the number of records in your file.` - ); +interface LoadState { + loading: boolean; + loadInProgress: boolean; + loadInProgressTrialRun: boolean; + hasLoadResultsTrialRun: boolean; + hasLoadResults: boolean; + inputFileDataTrialRun: any[]; + inputFileDataToLoad: any[]; } export interface LoadRecordsPerformLoadProps { @@ -83,40 +53,49 @@ export const LoadRecordsPerformLoad: FunctionComponent(0); - const [apiMode, setApiMode] = useState(() => getRecommendedApiMode(inputFileData.length, hasZipAttachment)); - const [bulkApiModeLabel] = useState(() => - getLabelWithOptionalRecommended('Bulk API', inputFileData.length > BATCH_RECOMMENDED_THRESHOLD, false) - ); - const [batchApiModeLabel] = useState(() => - getLabelWithOptionalRecommended('Batch API', inputFileData.length <= BATCH_RECOMMENDED_THRESHOLD, hasZipAttachment) - ); - const [batchSize, setBatchSize] = useState>(MAX_BULK); - const [batchSizeError, setBatchSizeError] = useState>(null); - const [insertNulls, setInsertNulls] = useState(false); - const [serialMode, setSerialMode] = useState(false); - const [dateFormat, setDateFormat] = useState(DATE_FORMATS.MM_DD_YYYY); - const [batchApiLimitError, setBatchApiLimitError] = useState>(null); - const [loading, setLoading] = useState(false); - const [loadInProgress, setLoadInProgress] = useState(false); - const [hasLoadResults, setHasLoadResults] = useState(false); + const [loadNumberTrialRun, setLoadNumberTrialRun] = useState(0); + + const [apiMode, setApiMode] = useRecoilState(loadRecordsState.apiModeState); + const [batchSize, setBatchSize] = useRecoilState(loadRecordsState.batchSizeState); + const [insertNulls, setInsertNulls] = useRecoilState(loadRecordsState.insertNullsState); + const [serialMode, setSerialMode] = useRecoilState(loadRecordsState.serialModeState); + const [trialRun, setTrialRun] = useRecoilState(loadRecordsState.trialRunState); + const [trialRunSize, setTrialRunSize] = useRecoilState(loadRecordsState.trialRunSizeState); + const [dateFormat, setDateFormat] = useRecoilState(loadRecordsState.dateFormatState); + /** Only show date hint if the user has a mapped date/datetime field */ + const hasDateFieldMapped = useRecoilValue(loadRecordsState.selectHasDateFieldMapped); + + const batchSizeError = useRecoilValue(loadRecordsState.selectBatchSizeError); + const batchApiLimitError = useRecoilValue(loadRecordsState.selectBatchApiLimitError); + const trialRunSizeError = useRecoilValue(loadRecordsState.selectTrialRunSizeError); + const bulkApiModeLabel = useRecoilValue(loadRecordsState.selectBulkApiModeLabel); + const batchApiModeLabel = useRecoilValue(loadRecordsState.selectBatchApiModeLabel); + const loadTypeLabel = startCase(loadType.toLowerCase()); - const numRecordsImpactedLabel = formatNumber(inputFileData.length); const [assignmentRuleId, setAssignmentRuleId] = useState>(null); + const [ + { loading, loadInProgress, loadInProgressTrialRun, hasLoadResultsTrialRun, hasLoadResults, inputFileDataTrialRun, inputFileDataToLoad }, + setLoadState, + ] = useState(() => ({ + loading: false, + loadInProgress: false, + loadInProgressTrialRun: false, + hasLoadResultsTrialRun: false, + hasLoadResults: false, + inputFileDataTrialRun: trialRun && trialRunSize ? inputFileData.slice(0, trialRunSize) : [], + inputFileDataToLoad: trialRun && trialRunSize ? inputFileData.slice(trialRunSize || 0) : inputFileData, + })); - // ensure that the Batch API does not consume an huge amount of API calls - useEffect(() => { - if (inputFileData.length && batchSize && inputFileData.length / batchSize > MAX_API_CALLS) { - setBatchApiLimitError(getBatchSizeExceededError(Math.round(inputFileData.length / batchSize))); - } else if (batchApiLimitError) { - setBatchApiLimitError(null); - } - }, [batchSize, inputFileData.length, batchApiLimitError, inputZipFileData]); + const numRecordsImpactedLabel = formatNumber(inputFileDataToLoad.length); + const numRecordsImpactedTrialRunLabel = formatNumber(inputFileDataTrialRun.length); - useEffect(() => { + useNonInitialEffect(() => { setBatchSize(getMaxBatchSize(apiMode)); - if (hasLoadResults) { - setHasLoadResults(false); - } + setLoadState((prevState) => ({ + ...prevState, + hasLoadResults: false, + hasLoadResultsTrialRun: false, + })); if (apiMode === 'BATCH' && !serialMode) { setSerialMode(true); } else if (apiMode === 'BULK' && serialMode) { @@ -125,16 +104,15 @@ export const LoadRecordsPerformLoad: FunctionComponent { - // Hack to ensure that the apiMode us fully changed before checking batch size - setTimeout(() => { - if (!isNumber(batchSize) || batchSize <= 0 || batchSize > getMaxBatchSize(apiMode)) { - setBatchSizeError(`The batch size must be between 1 and ${getMaxBatchSize(apiMode)}`); - } else if (batchSizeError) { - setBatchSizeError(null); - } - }); - }, [batchSize, apiMode, batchSizeError]); + useNonInitialEffect(() => { + setLoadState((prevState) => ({ + ...prevState, + hasLoadResults: false, + hasLoadResultsTrialRun: false, + inputFileDataTrialRun: trialRun && trialRunSize ? inputFileData.slice(0, trialRunSize) : [], + inputFileDataToLoad: trialRun && trialRunSize ? inputFileData.slice(trialRunSize || 0) : inputFileData, + })); + }, [trialRun, trialRunSize, inputFileData]); function handleBatchSize(event: ChangeEvent) { const value = Number.parseInt(event.target.value); @@ -145,21 +123,33 @@ export const LoadRecordsPerformLoad: FunctionComponent) { - setDateFormat(event.target.value); + function handletrialRunSize(event: ChangeEvent) { + const value = Number.parseInt(event.target.value); + if (Number.isInteger(value)) { + setTrialRunSize(value); + } else if (!event.target.value) { + setTrialRunSize(null); + } } - async function handleStartLoad() { + async function handleStartLoad(isTrialRun = false) { if ( loadNumber === 0 || (await ConfirmationModalPromise({ content: 'This file has already been loaded, are you sure you want to load it again?', })) ) { - setLoadNumber(loadNumber + 1); - setLoading(true); - setLoadInProgress(true); - setHasLoadResults(false); + if (isTrialRun) { + setLoadNumberTrialRun(loadNumberTrialRun + 1); + } else { + setLoadNumber(loadNumber + 1); + } + setLoadState((prevState) => { + if (isTrialRun) { + return { ...prevState, loading: true, loadInProgressTrialRun: true, hasLoadResultsTrialRun: false, hasLoadResults: false }; + } + return { ...prevState, loading: true, loadInProgress: true, hasLoadResults: false }; + }); onIsLoading(true); trackEvent(ANALYTICS_KEYS.load_Submitted, { loadType, @@ -168,31 +158,33 @@ export const LoadRecordsPerformLoad: FunctionComponent type === 'STATIC').length, }); document.title = `Loading Records | ${TITLES.BAR_JETSTREAM}`; } } - function handleFinishLoad({ success, failure }: { success: number; failure: number }) { - setLoading(false); - setHasLoadResults(true); - setLoadInProgress(false); + function handleFinishLoad({ success, failure }: { success: number; failure: number }, isTrialRun = false) { + setLoadState((prevState) => { + if (isTrialRun) { + return { ...prevState, loading: false, loadInProgressTrialRun: false, hasLoadResultsTrialRun: true }; + } + return { ...prevState, loading: false, loadInProgress: false, hasLoadResults: true }; + }); onIsLoading(false); document.title = `${formatNumber(success)} Success - ${formatNumber(failure)} Failed ${TITLES.BAR_JETSTREAM}`; } function hasDataInputError(): boolean { - return !!batchSizeError || !!batchApiLimitError; + return !!batchSizeError || !!batchApiLimitError || (trialRun && !!trialRunSizeError); } - /** - * TODO: - * limit batch api based on number of records and batch size (maybe limit to 50 or 100 api calls total)? - */ - return (
    @@ -237,8 +229,8 @@ export const LoadRecordsPerformLoad: FunctionComponent @@ -265,7 +257,7 @@ export const LoadRecordsPerformLoad: FunctionComponent - - - - - - + + + + + )} + + {!inputZipFileData && ( + <> + + + {trialRun && ( + + + + )} + + )}

    Summary

    @@ -307,26 +349,63 @@ export const LoadRecordsPerformLoad: FunctionComponent {selectedOrg.username}
    -
    - -
    + + {trialRun && ( +
    + +
    + )} +
    + +
    +

    Results

    + {/* DRY RUN LOAD */} + {trialRun && (loadInProgressTrialRun || hasLoadResultsTrialRun) && ( + handleFinishLoad(results, true)} + /> + )} + {/* STANDARD LOAD */} {(loadInProgress || hasLoadResults) && ( handleFinishLoad(results)} /> )}
    diff --git a/apps/jetstream/src/app/components/load-records/steps/PerformLoadCustomMetadata.tsx b/apps/jetstream/src/app/components/load-records/steps/PerformLoadCustomMetadata.tsx index 6a6de8455..2996996bd 100644 --- a/apps/jetstream/src/app/components/load-records/steps/PerformLoadCustomMetadata.tsx +++ b/apps/jetstream/src/app/components/load-records/steps/PerformLoadCustomMetadata.tsx @@ -4,17 +4,17 @@ import { getMapOf } from '@jetstream/shared/utils'; import { DeployMessage, Maybe, SalesforceOrgUi, SalesforceOrgUiType } from '@jetstream/types'; import { Badge, Checkbox, ConfirmationModalPromise, FileDownloadModal, SalesforceLogin, Select, Spinner } from '@jetstream/ui'; import { ChangeEvent, Fragment, FunctionComponent, useCallback, useState } from 'react'; -import { useRecoilState } from 'recoil'; -import { applicationCookieState } from '../../../app-state'; -import { useAmplitude } from '../../core/analytics'; +import { useRecoilValue } from 'recoil'; +import { applicationCookieState, selectSkipFrontdoorAuth } from '../../../app-state'; import ConfirmPageChange from '../../core/ConfirmPageChange'; +import { useAmplitude } from '../../core/analytics'; import * as fromJetstreamEvents from '../../core/jetstream-events'; import { DownloadType } from '../../shared/load-records-results/load-records-results-types'; +import { useDeployMetadataPackage } from '../../shared/useDeployMetadataPackage'; +import LoadRecordsDuplicateWarning from '../components/LoadRecordsDuplicateWarning'; import LoadRecordsCustomMetadataResultsTable from '../components/load-results/LoadRecordsCustomMetadataResultsTable'; import LoadRecordsResultsModal from '../components/load-results/LoadRecordsResultsModal'; -import LoadRecordsDuplicateWarning from '../components/LoadRecordsDuplicateWarning'; import { DownloadModalData, FieldMapping, FieldWithRelatedEntities, MapOfCustomMetadataRecord, ViewModalData } from '../load-records-types'; -import { useDeployMetadataPackage } from '../useDeployMetadataPackage'; import { convertCsvToCustomMetadata, prepareCustomMetadata } from '../utils/load-records-utils'; export function getDeploymentStatusUrl(id: string) { @@ -49,7 +49,8 @@ export const PerformLoadCustomMetadata: FunctionComponent { const rollbar = useRollbar(); const { trackEvent } = useAmplitude(); - const [{ serverUrl, defaultApiVersion, google_apiKey, google_appId, google_clientId }] = useRecoilState(applicationCookieState); + const { serverUrl, defaultApiVersion, google_apiKey, google_appId, google_clientId } = useRecoilValue(applicationCookieState); + const skipFrontDoorAuth = useRecoilValue(selectSkipFrontdoorAuth); const [loadNumber, setLoadNumber] = useState(0); const [rollbackOnError, setRollbackOnError] = useState(false); const [dateFormat, setDateFormat] = useState(DATE_FORMATS.MM_DD_YYYY); @@ -229,7 +230,7 @@ export const PerformLoadCustomMetadata: FunctionComponent Custom metadata records require all fields to be set, any unmapped or null fields will result in null values. @@ -292,7 +293,13 @@ export const PerformLoadCustomMetadata: FunctionComponent {deployStatusUrl && ( - + View job in Salesforce )} diff --git a/apps/jetstream/src/app/components/load-records/steps/SelectObjectAndFile.tsx b/apps/jetstream/src/app/components/load-records/steps/SelectObjectAndFile.tsx index 0a69483c1..d6f1b1691 100644 --- a/apps/jetstream/src/app/components/load-records/steps/SelectObjectAndFile.tsx +++ b/apps/jetstream/src/app/components/load-records/steps/SelectObjectAndFile.tsx @@ -12,11 +12,11 @@ import { Grid, GridCol, XlsxSheetSelectionModalPromise, + fireToast, } from '@jetstream/ui'; import type { DescribeGlobalSObjectResult } from 'jsforce'; import isString from 'lodash/isString'; import { FunctionComponent } from 'react'; -import { fireToast } from '../../core/AppToast'; import LoadRecordsLoadTypeButtons from '../components/LoadRecordsLoadTypeButtons'; import { FieldWithRelatedEntities, LocalOrGoogle } from '../load-records-types'; import { filterLoadSobjects } from '../utils/load-records-utils'; @@ -75,16 +75,19 @@ export const LoadRecordsSelectObjectAndFile: FunctionComponent { const hasGoogleInputConfigured = !!googleApiConfig?.apiKey && !!googleApiConfig?.appId && !!googleApiConfig?.clientId; - async function handleFile({ content, filename, isPasteFromClipboard }: InputReadFileContent) { + async function handleFile({ content, filename, isPasteFromClipboard, extension }: InputReadFileContent) { try { - const { data, headers, errors } = await parseFile(content, { onParsedMultipleWorkbooks, isPasteFromClipboard }); + const { data, headers, errors } = await parseFile(content, { onParsedMultipleWorkbooks, isPasteFromClipboard, extension }); onFileChange(data, headers, filename, 'local'); if (errors.length > 0) { logger.warn(errors); - fireToast({ - message: 'There were errors parsing the file. Check the file preview to ensure the data is correct.', - type: 'warning', - }); + // suppress delimiter error if it is the only error and just one column of data + if (headers.length !== 1 || errors.length !== 1 || !errors[0].includes('auto-detect delimiting character')) { + fireToast({ + message: `There were errors parsing the file. Check the file preview to ensure the data is correct. ${errors.join()}`, + type: 'warning', + }); + } } } catch (ex) { logger.warn('Error reading file', ex); @@ -163,7 +166,7 @@ export const LoadRecordsSelectObjectAndFile: FunctionComponent - {allowBinaryAttachment && inputZipFilename && ( + {allowBinaryAttachment && ( diff --git a/apps/jetstream/src/app/components/load-records/load-records.worker.ts b/apps/jetstream/src/app/components/load-records/utils/load-records-process.ts similarity index 67% rename from apps/jetstream/src/app/components/load-records/load-records.worker.ts rename to apps/jetstream/src/app/components/load-records/utils/load-records-process.ts index 063a1f74e..f24ba0d07 100644 --- a/apps/jetstream/src/app/components/load-records/load-records.worker.ts +++ b/apps/jetstream/src/app/components/load-records/utils/load-records-process.ts @@ -1,17 +1,8 @@ -/// -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { LoadRecordsBatchError, fetchMappedRelatedRecords, generateCsv, transformData } from '@jetstream/shared/browser-worker-utils'; import { logger } from '@jetstream/shared/client-logger'; -import { - bulkApiAddBatchToJob, - bulkApiCloseJob, - bulkApiCreateJob, - bulkApiGetJob, - genericRequest, - initForElectron, -} from '@jetstream/shared/data'; +import { bulkApiAddBatchToJob, bulkApiCloseJob, bulkApiCreateJob, bulkApiGetJob, genericRequest } from '@jetstream/shared/data'; +import { generateCsv } from '@jetstream/shared/ui-utils'; import { getHttpMethod, getSizeInMbFromBase64, splitArrayToMaxSize } from '@jetstream/shared/utils'; -import type { +import { BulkJobBatchInfo, BulkJobWithBatches, HttpMethod, @@ -20,102 +11,60 @@ import type { SobjectCollectionRequest, SobjectCollectionRequestRecord, SobjectCollectionResponse, - WorkerMessage, } from '@jetstream/types'; import JSZip from 'jszip'; import isString from 'lodash/isString'; -import type { - LoadDataBulkApi, - LoadDataBulkApiStatusPayload, - LoadDataPayload, - PrepareDataPayload, -} from '../../components/load-records/load-records-types'; -import { axiosElectronAdapter, initMessageHandler } from '../core/electron-axios-adapter'; - -declare const self: DedicatedWorkerGlobalScope; -logger.log('[LOAD WORKER] INITIALIZED'); - -self.addEventListener('error', (event) => { - console.log('WORKER ERROR', event); -}); - -type MessageName = 'isElectron' | 'prepareData' | 'prepareDataProgress' | 'loadData' | 'loadDataStatus' | 'abort'; - -const abortStatus = { isAborted: false }; +import type { LoadDataBulkApi, LoadDataBulkApiStatusPayload, LoadDataPayload, PrepareDataPayload } from '../load-records-types'; +import { LoadRecordsBatchError, fetchMappedRelatedRecords, transformData } from './load-records-utils'; -// Respond to message from parent thread -self.addEventListener('message', (event) => { - const payload: WorkerMessage = event.data; - logger.info('[WORKER]', { payload }); - handleMessage(payload.name, payload.data, event.ports?.[0]); -}); +/** + * Pre-process all load data to prepare for loading + * + * @param payloadData + * @param progressCallback + * @returns + */ +export async function prepareData(payloadData: PrepareDataPayload, progressCallback: (progress: number) => void) { + const { data, fieldMapping, sObject, dateFormat, apiMode } = payloadData; -async function handleMessage(name: MessageName, payloadData: any, port?: MessagePort) { - try { - abortStatus.isAborted = false; - switch (name) { - case 'isElectron': { - initForElectron(axiosElectronAdapter); - initMessageHandler(port); - break; - } - case 'prepareData': { - payloadData = payloadData || {}; - const { data, fieldMapping, sObject, dateFormat, apiMode } = payloadData as PrepareDataPayload; - if (!Array.isArray(data) || !fieldMapping || !isString(sObject) || !isString(dateFormat) || !isString(apiMode)) { - throw new Error('The required parameters were not included in the request'); - } + if (!Array.isArray(data) || !fieldMapping || !isString(sObject) || !isString(dateFormat) || !isString(apiMode)) { + throw new Error('The required parameters were not included in the request'); + } - // also need to change file to add `#` at the beginning each data point - const preparedData = await fetchMappedRelatedRecords(transformData(payloadData), payloadData, (progress: number) => { - replyToMessage('prepareDataProgress', { progress }); - }); + const preparedData = await fetchMappedRelatedRecords(await transformData(payloadData), payloadData, progressCallback); - replyToMessage(name, { preparedData }); - break; - } - case 'loadData': { - const { apiMode } = payloadData as LoadDataPayload; - if (apiMode === 'BULK') { - // BULK - emits LoadDataBulkApiStatusPayload - loadBulkApiData(payloadData as LoadDataPayload); - } else { - // BATCH - emits {records: RecordResultWithRecord[]} - loadBatchApiData(payloadData as LoadDataPayload); - } - break; - } - case 'abort': { - abortStatus.isAborted = true; - break; - } - default: - break; - } - } catch (ex) { - return replyToMessage(name, null, new Error(ex.message)); - } + return preparedData; } -async function loadBulkApiData({ org, data, sObject, type, batchSize, externalId, assignmentRuleId, serialMode }: LoadDataPayload) { - const replyName = 'loadData'; +/** + * Load data using the BULK API + * + * @param param0 + * @param statusCallback + * @returns + */ +export async function loadBulkApiData( + { org, data, sObject, type, batchSize, externalId, assignmentRuleId, serialMode }: LoadDataPayload, + statusCallback: (resultsSummary: LoadDataBulkApiStatusPayload) => void, + checkIfAborted: () => boolean +) { try { const results = await bulkApiCreateJob(org, { type, sObject, serialMode, assignmentRuleId, externalId }); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const jobId = results.id!; let batches: LoadDataBulkApi[] = []; batches = splitArrayToMaxSize(data, batchSize) - .map((batch) => generateCsv(batch)) + .map((batch) => generateCsv(batch, { delimiter: ',' })) .map((data, i) => ({ data, batchNumber: i, completed: false, success: false })); - replyToMessage('loadDataStatus', { resultsSummary: getBatchSummary(results, batches) }); + statusCallback(getBatchSummary(results, batches)); let currItem = 1; let fatalError = false; const loadErrors: Error[] = []; const batchOrderMap: MapOf = {}; for (const batch of batches) { try { - if (abortStatus.isAborted) { + if (checkIfAborted()) { throw new Error('Aborted'); } const batchResult = await bulkApiAddBatchToJob(org, jobId, batch.data, currItem === batches.length); @@ -131,11 +80,7 @@ async function loadBulkApiData({ org, data, sObject, type, batchSize, externalId batch.errorMessage = ex.message; loadErrors.push(ex); } finally { - let transfer: Transferable[] | undefined = undefined; - if (batch.data instanceof ArrayBuffer) { - transfer = [batch.data]; - } - replyToMessage('loadDataStatus', { resultsSummary: getBatchSummary(results, batches) }, null, transfer); + statusCallback(getBatchSummary(results, batches)); } currItem++; } @@ -154,28 +99,35 @@ async function loadBulkApiData({ org, data, sObject, type, batchSize, externalId fatalError = true; } - replyToMessage( - replyName, - { jobInfo: jobInfoWithBatches }, - (fatalError && new LoadRecordsBatchError(`One or more batches failed to load`, loadErrors)) || null - ); - if (jobInfoWithBatches.state === 'Open') { // close job last so user does not have to wait for this since it does not matter - try { - await bulkApiCloseJob(org, jobId); - } catch (ex) { - // ignore batch closure failures - } + bulkApiCloseJob(org, jobId).catch((ex) => { + logger.warn('Error closing job', ex); + }); } + + return { + jobInfo: jobInfoWithBatches, + loadError: fatalError || loadErrors.length ? new LoadRecordsBatchError(`One or more batches failed to load`, loadErrors) : null, + }; } catch (ex) { - return replyToMessage(replyName, null, new Error(ex.message)); + logger.error('Error loading data', ex); + throw ex; } } -async function loadBatchApiData(payload: LoadDataPayload) { +/** + * Load data using the Composite API (AKA Batch API) + * + * @param payload + * @param statusCallback + */ +export async function loadBatchApiData( + payload: LoadDataPayload, + statusCallback: (records: RecordResultWithRecord[]) => void, + checkIfAborted: () => boolean +) { const { org, sObject, type, externalId, assignmentRuleId } = payload; - const replyName = 'loadData'; try { const { batchRecordMap, batches, failedRecords } = await getBatchApiBatches(payload); @@ -197,7 +149,7 @@ async function loadBatchApiData(payload: LoadDataPayload) { let recordIndexesWithMissingIds: Set = new Set(); try { - if (abortStatus.isAborted) { + if (checkIfAborted()) { throw new Error('Aborted'); } if (type === 'DELETE') { @@ -253,7 +205,7 @@ async function loadBatchApiData(payload: LoadDataPayload) { } catch (ex) { let message = `An unknown error has occurred. Salesforce Message: ${ex.message}`; let statusCode = 'UNKNOWN'; - if (abortStatus.isAborted) { + if (checkIfAborted()) { message = 'Data load aborted'; statusCode = 'ABORTED'; } @@ -272,13 +224,14 @@ async function loadBatchApiData(payload: LoadDataPayload) { }) ) || []; } finally { - replyToMessage('loadDataStatus', { records: responseWithRecord }); + // replyToMessage('loadDataStatus', { records: responseWithRecord }); + statusCallback(responseWithRecord); } } // Handle and processing failures (these happen when processing binary data) if (failedRecords.length) { - replyToMessage('loadDataStatus', { - records: failedRecords.map( + statusCallback( + failedRecords.map( (record): RecordResultWithRecord => ({ success: false, errors: [ @@ -290,22 +243,15 @@ async function loadBatchApiData(payload: LoadDataPayload) { ], record, }) - ), - }); + ) + ); } - replyToMessage(replyName, {}); } catch (ex) { - return replyToMessage(replyName, null, ex); + logger.error('Error loading data', ex); + throw ex; } } -/** - * Handles preparing batches for the Batch API - * If required, prepares zip attachments as base64 and calculates the batch size - * - * @param {LoadDataPayload} payload - * @returns - */ async function getBatchApiBatches({ data, sObject, @@ -385,9 +331,3 @@ function getBatchSummary(results: BulkJobWithBatches, batches: LoadDataBulkApi[] batchSummary: batches.map(({ id, batchNumber, completed, success }) => ({ id, batchNumber, completed, success })), }; } - -function replyToMessage(name: string, data: any, error?: any, transfer?: Transferable[]) { - transfer ? self.postMessage({ name, data, error }, transfer) : self.postMessage({ name, data, error }); -} - -export default null as any; diff --git a/apps/jetstream/src/app/components/load-records/utils/load-records-utils.ts b/apps/jetstream/src/app/components/load-records/utils/load-records-utils.tsx similarity index 82% rename from apps/jetstream/src/app/components/load-records/utils/load-records-utils.ts rename to apps/jetstream/src/app/components/load-records/utils/load-records-utils.tsx index 961eff77c..26dff37e6 100644 --- a/apps/jetstream/src/app/components/load-records/utils/load-records-utils.ts +++ b/apps/jetstream/src/app/components/load-records/utils/load-records-utils.tsx @@ -2,17 +2,21 @@ import { logger } from '@jetstream/shared/client-logger'; import { SFDC_BULK_API_NULL_VALUE } from '@jetstream/shared/constants'; import { queryAll, queryWithCache } from '@jetstream/shared/data'; import { describeSObjectWithExtendedTypes, formatNumber, isRelationshipField } from '@jetstream/shared/ui-utils'; -import { REGEX, getMapOf, transformRecordForDataLoad } from '@jetstream/shared/utils'; +import { REGEX, delay, getMapOf, transformRecordForDataLoad } from '@jetstream/shared/utils'; import { EntityParticleRecord, FieldWithExtendedType, InsertUpdateUpsertDelete, MapOf, Maybe, SalesforceOrgUi } from '@jetstream/types'; import type { DescribeGlobalSObjectResult, DescribeSObjectResult } from 'jsforce'; import JSZip from 'jszip'; import groupBy from 'lodash/groupBy'; import isNil from 'lodash/isNil'; import isString from 'lodash/isString'; -import { Query, WhereClause, composeQuery, getField } from 'soql-parser-js'; +import uniqueId from 'lodash/uniqueId'; +import { Query, WhereClauseWithRightCondition, WhereClauseWithoutOperator, composeQuery, getField } from 'soql-parser-js'; import type { + ApiMode, FieldMapping, FieldMappingItem, + FieldMappingItemCsv, + FieldMappingItemStatic, FieldRelatedEntity, FieldWithRelatedEntities, MapOfCustomMetadataRecord, @@ -23,6 +27,11 @@ import type { import { LoadSavedMappingItem } from '../load-records.state'; export const SELF_LOOKUP_KEY = '~SELF_LOOKUP~'; +export const STATIC_MAPPING_PREFIX = '~STATIC~MAPPING~'; +export const BATCH_RECOMMENDED_THRESHOLD = 2000; +export const MAX_API_CALLS = 250; +export const MAX_BULK = 10000; +export const MAX_BATCH = 200; const DEFAULT_NON_EXT_ID_MAPPING_OPT: NonExtIdLookupOption = 'ERROR_IF_MULTIPLE'; const DEFAULT_NULL_IF_NO_MATCH_MAPPING_OPT = false; @@ -76,6 +85,7 @@ export async function getFieldMetadata(org: SalesforceOrgUi, sobject: string): P typeLabel: field.typeLabel, referenceTo, relationshipName, + field, }; }); @@ -86,7 +96,7 @@ export async function getFieldMetadata(org: SalesforceOrgUi, sobject: string): P // related records if (relatedObjects.size > 0) { const relatedEntities = ( - await queryWithCache(org, getExternalIdFieldsForSobjectsQuery(Array.from(relatedObjects)), true) + await queryWithCache(org, getExternalIdFieldsForSobjectsQuery(Array.from(relatedObjects))) ).data; const relatedEntitiesByObj = groupBy(relatedEntities.queryResults.records, 'EntityDefinition.QualifiedApiName'); fieldsWithRelationships.forEach((field) => { @@ -111,6 +121,36 @@ export async function getFieldMetadata(org: SalesforceOrgUi, sobject: string): P return fields; } +export function getRecommendedApiMode(numRecords: number, hasBinaryAttachment: boolean): ApiMode { + return !hasBinaryAttachment && numRecords > BATCH_RECOMMENDED_THRESHOLD ? 'BULK' : 'BATCH'; +} + +export function getLabelWithOptionalRecommended(label: string, recommended: boolean, required: boolean): string | JSX.Element { + if (!recommended && !required) { + return label; + } + if (required) { + return ( + + {label} (Required based on the load configuration) + + ); + } + return ( + + {label} (Recommended based on the number of impacted records) + + ); +} + +export function getMaxBatchSize(apiMode: ApiMode): number { + if (apiMode === 'BATCH') { + return MAX_BATCH; + } else { + return MAX_BULK; + } +} + /** * Attempt to auto-match CSV fields to object fields * 1. exact API name match @@ -122,7 +162,13 @@ export async function getFieldMetadata(org: SalesforceOrgUi, sobject: string): P * @param inputHeader * @param fields */ -export function autoMapFields(inputHeader: string[], fields: FieldWithRelatedEntities[], binaryBodyField: Maybe): FieldMapping { +export function autoMapFields( + inputHeader: string[], + fields: FieldWithRelatedEntities[], + binaryBodyField: Maybe, + loadType: InsertUpdateUpsertDelete, + externalId?: Maybe +): FieldMapping { const output: FieldMapping = {}; const fieldVariations: MapOf = {}; const fieldLabelVariations: MapOf = {}; @@ -159,6 +205,7 @@ export function autoMapFields(inputHeader: string[], fields: FieldWithRelatedEnt fieldLabelVariations[lowercaseFieldOrRelationship.replace(REGEX.NOT_ALPHANUMERIC, '')]; output[field] = { + type: 'CSV', csvField: field, targetField: matchedField?.name || null, mappedToLookup: false, @@ -203,12 +250,13 @@ export function autoMapFields(inputHeader: string[], fields: FieldWithRelatedEnt } }); - return checkForDuplicateFieldMappings(output); + return checkFieldsForMappingError(output, loadType, externalId); } export function resetFieldMapping(inputHeader: string[]): FieldMapping { return inputHeader.reduce((output: FieldMapping, field) => { output[field] = { + type: 'CSV', csvField: field, targetField: null, mappedToLookup: false, @@ -222,6 +270,22 @@ export function resetFieldMapping(inputHeader: string[]): FieldMapping { }, {}); } +export function initStaticFieldMappingItem(): FieldMappingItemStatic { + const csvValuePlaceholder = uniqueId(STATIC_MAPPING_PREFIX); + return { + type: 'STATIC', + csvField: csvValuePlaceholder, + staticValue: '', + targetField: null, + mappedToLookup: false, + fieldMetadata: undefined, + selectedReferenceTo: undefined, + lookupOptionUseFirstMatch: DEFAULT_NON_EXT_ID_MAPPING_OPT, + lookupOptionNullIfNoMatch: DEFAULT_NULL_IF_NO_MATCH_MAPPING_OPT, + isBinaryBodyField: false, + }; +} + export function loadFieldMappingFromSavedMapping( savedMapping: LoadSavedMappingItem, inputHeader: string[], @@ -229,7 +293,7 @@ export function loadFieldMappingFromSavedMapping( binaryBodyField: Maybe ): FieldMapping { const fieldMetadataByName = getMapOf(fields, 'name'); - return inputHeader.reduce((output: FieldMapping, field) => { + const newMapping = inputHeader.reduce((output: FieldMapping, field) => { const matchedMapping = savedMapping.mapping[field]; if (matchedMapping && matchedMapping.targetField && fieldMetadataByName[matchedMapping.targetField]) { output[field] = { @@ -238,9 +302,10 @@ export function loadFieldMappingFromSavedMapping( ...fieldMetadataByName[matchedMapping.targetField], }, isBinaryBodyField: !!binaryBodyField && matchedMapping.targetField === binaryBodyField, - }; + } as FieldMappingItemCsv; } else { output[field] = { + type: 'CSV', csvField: field, targetField: null, mappedToLookup: false, @@ -253,6 +318,34 @@ export function loadFieldMappingFromSavedMapping( } return output; }, {}); + + Object.keys(savedMapping.mapping) + .filter((key) => key.startsWith(STATIC_MAPPING_PREFIX)) + .forEach((field) => { + const mapping = savedMapping.mapping[field]; + if (mapping.targetField) { + newMapping[field] = { + ...mapping, + type: 'STATIC', + fieldMetadata: { + ...fieldMetadataByName[mapping.targetField], + }, + isBinaryBodyField: false, + } as FieldMappingItemStatic; + } + }); + + return newMapping; +} + +export function checkFieldsForMappingError( + fieldMapping: FieldMapping, + loadType: InsertUpdateUpsertDelete, + externalId?: Maybe +): FieldMapping { + fieldMapping = checkForDuplicateFieldMappings(fieldMapping); + fieldMapping = checkForExternalIdFieldMappingsError(fieldMapping, loadType, externalId); + return fieldMapping; } export function checkForDuplicateFieldMappings(fieldMapping: FieldMapping): FieldMapping { @@ -268,9 +361,11 @@ export function checkForDuplicateFieldMappings(fieldMapping: FieldMapping): Fiel }, {}); Object.keys(fieldMapping).forEach((key) => { if (fieldMapping[key].targetField) { + const isDuplicateMappedField = (mappedFieldFrequency[fieldMapping[key].targetField || ''] || 0) > 1; fieldMapping[key] = { ...fieldMapping[key], - isDuplicateMappedField: (mappedFieldFrequency[fieldMapping[key].targetField || ''] || 0) > 1, + isDuplicateMappedField, + fieldErrorMsg: isDuplicateMappedField ? 'Each Salesforce field should only be mapped once' : undefined, }; } else { fieldMapping[key] = { ...fieldMapping[key], isDuplicateMappedField: false }; @@ -279,6 +374,26 @@ export function checkForDuplicateFieldMappings(fieldMapping: FieldMapping): Fiel return fieldMapping; } +export function checkForExternalIdFieldMappingsError( + fieldMapping: FieldMapping, + loadType: InsertUpdateUpsertDelete, + externalId?: Maybe +): FieldMapping { + if (loadType !== 'UPSERT' || !externalId || externalId === 'Id') { + return fieldMapping; + } + fieldMapping = { ...fieldMapping }; + Object.keys(fieldMapping).forEach((key) => { + if (fieldMapping[key].targetField === 'Id' && !fieldMapping[key].fieldErrorMsg) { + fieldMapping[key] = { + ...fieldMapping[key], + fieldErrorMsg: 'Including a Record Id in an upsert will cause the load to fail', + }; + } + }); + return fieldMapping; +} + function getExternalIdFieldsForSobjectsQuery(sobjects: string[]) { const soql = composeQuery({ fields: [ @@ -353,9 +468,19 @@ export function getFieldHeaderFromMapping(fieldMapping: FieldMapping): string[] }); } -export function transformData({ data, fieldMapping, sObject, insertNulls, dateFormat, apiMode }: PrepareDataPayload): any[] { - return data.map((row) => { - return Object.keys(fieldMapping) +export async function transformData({ data, fieldMapping, sObject, insertNulls, dateFormat, apiMode }: PrepareDataPayload): Promise { + const output: any[] = []; + let counter = 0; + + for (const row of data) { + counter++; + /** + * Let other work get done every 1K records to avoid blocking the UI + */ + if (counter % 1000 === 0) { + await delay(0); + } + const processedRow = Object.keys(fieldMapping) .filter((key) => !!fieldMapping[key].targetField) .reduce((output: any, field, i) => { if (apiMode === 'BATCH' && i === 0) { @@ -364,7 +489,7 @@ export function transformData({ data, fieldMapping, sObject, insertNulls, dateFo let skipField = false; const fieldMappingItem = fieldMapping[field]; // SFDC handles automatic type conversion with both bulk and batch apis (if possible, otherwise the record errors) - let value = row[field]; + let value = fieldMappingItem.type === 'STATIC' ? fieldMappingItem.staticValue : row[field]; if (isNil(value) || (isString(value) && !value)) { if (apiMode === 'BULK' && insertNulls) { @@ -419,7 +544,10 @@ export function transformData({ data, fieldMapping, sObject, insertNulls, dateFo return output; }, {}); - }); + + output.push(processedRow); + } + return output; } /** @@ -475,7 +603,7 @@ export async function fetchMappedRelatedRecords( fieldRelationshipName = `${targetLookupField}`; } // remove any falsy values, related fields cannot be booleans or numbers, so this should not cause issues - const relatedValues = new Set(data.map((row) => row[targetField || '']).filter(Boolean)); + const relatedValues = new Set(data.map((row) => row[targetField || '']).filter((value) => !!value && isString(value))); if (relatedValues.size && selectedReferenceTo && targetLookupField) { const relatedRecordsByRelatedField: MapOf = {}; @@ -562,8 +690,8 @@ function getRelatedFieldsQueries(baseObject: string, relatedObject: string, rela fields: Array.from(new Set([getField('Id'), getField(relatedField)])), }; - let extraWhereClauseNew: WhereClause | undefined = undefined; - const whereClause: WhereClause = { + let extraWhereClauseNew: WhereClauseWithoutOperator | WhereClauseWithRightCondition | undefined = undefined; + const whereClause: WhereClauseWithoutOperator = { left: { field: relatedField, operator: 'IN', @@ -649,8 +777,27 @@ export function convertCsvToCustomMetadata( }, {}); inputFileData.forEach((row) => { - const fullName = `${selectedSObject}.${row[fieldMappingByTargetField.DeveloperName.csvField]}`; - const label = fieldMappingByTargetField.Label ? row[fieldMappingByTargetField.Label.csvField] : null; + if (!fieldMappingByTargetField.DeveloperName.csvField || !fieldMappingByTargetField.Label.csvField) { + return; + } + + let developerName: string | null = null; + if (fieldMappingByTargetField.Label) { + developerName = + fieldMappingByTargetField.DeveloperName.type === 'STATIC' + ? fieldMappingByTargetField.DeveloperName.staticValue + : row[fieldMappingByTargetField.DeveloperName.csvField]; + } + + let label: string | null = null; + if (fieldMappingByTargetField.Label) { + label = + fieldMappingByTargetField.Label.type === 'STATIC' + ? fieldMappingByTargetField.Label.staticValue + : row[fieldMappingByTargetField.Label.csvField]; + } + + const fullName = `${selectedSObject}.${developerName}`; const record: any = { DeveloperName: fullName, Label: label, @@ -665,8 +812,16 @@ export function convertCsvToCustomMetadata( .filter((field) => field.name.endsWith('__c')) .forEach((field) => { const fieldMappingItem = fieldMappingByTargetField[field.name]; - let fieldValue = fieldMappingItem ? row[fieldMappingItem.csvField] : null; - // ensure that field is in correct data type (mostly for dates) + + let fieldValue: string | null | boolean = null; + + if (fieldMappingItem && fieldMappingItem.type === 'STATIC') { + fieldValue = fieldMappingItem.staticValue; + } else if (fieldMappingItem) { + fieldValue = row[fieldMappingItem.csvField]; + } + + // ensure field is in correct data type (mostly for dates) fieldValue = transformRecordForDataLoad(fieldValue, field.type, dateFormat); // Custom metadata lookups always use name to relate records const soapType = field.soapType === 'tns:ID' ? 'xsd:string' : field.soapType; @@ -737,7 +892,7 @@ export function checkForDuplicateRecords( } if (mappingItem && mappingItem.targetField) { - const rowsByMappedKeyField = groupBy(inputFileData, mappingItem.csvField); + const rowsByMappedKeyField = groupBy(inputFileData, mappingItem.csvField || 'static'); return { duplicateKey: mappingItem.csvField === mappingItem.targetField ? mappingItem.csvField : `${mappingItem.csvField} -> ${mappingItem.targetField}`, diff --git a/apps/jetstream/src/app/components/load-records/utils/load-records-worker.ts b/apps/jetstream/src/app/components/load-records/utils/load-records-worker.ts deleted file mode 100644 index 1a4463fa1..000000000 --- a/apps/jetstream/src/app/components/load-records/utils/load-records-worker.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { externalMessagePorts } from '../../core/electron-utils'; - -const loadWorker = new Worker(new URL('../load-records.worker.ts', import.meta.url), { type: 'module' }); - -if (loadWorker && window.electron?.isElectron) { - externalMessagePorts.loadWorkerPort - ? loadWorker.postMessage({ name: 'isElectron' }, [externalMessagePorts.loadWorkerPort]) - : loadWorker.postMessage({ name: 'isElectron' }, []); -} - -export function getLoadWorker() { - return loadWorker; -} diff --git a/apps/jetstream/src/app/components/manage-permissions/ManagePermissions.tsx b/apps/jetstream/src/app/components/manage-permissions/ManagePermissions.tsx index 105e2e22d..e2ed0164d 100644 --- a/apps/jetstream/src/app/components/manage-permissions/ManagePermissions.tsx +++ b/apps/jetstream/src/app/components/manage-permissions/ManagePermissions.tsx @@ -25,6 +25,7 @@ export const ManagePermissions: FunctionComponent = () = const resetFieldsByKey = useResetRecoilState(fromPermissionsState.fieldsByKey); const resetObjectPermissionMap = useResetRecoilState(fromPermissionsState.objectPermissionMap); const resetFieldPermissionMap = useResetRecoilState(fromPermissionsState.fieldPermissionMap); + const resetTabVisibilityPermissionMap = useResetRecoilState(fromPermissionsState.tabVisibilityPermissionMap); const [priorSelectedOrg, setPriorSelectedOrg] = useState(null); const hasSelectionsMade = useRecoilValue(fromPermissionsState.hasSelectionsMade); @@ -45,6 +46,7 @@ export const ManagePermissions: FunctionComponent = () = resetFieldsByKey(); resetObjectPermissionMap(); resetFieldPermissionMap(); + resetTabVisibilityPermissionMap(); } else if (!selectedOrg) { resetProfilesState(); resetSelectedProfilesPermSetState(); @@ -56,6 +58,7 @@ export const ManagePermissions: FunctionComponent = () = resetFieldsByKey(); resetObjectPermissionMap(); resetFieldPermissionMap(); + resetTabVisibilityPermissionMap(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedOrg, priorSelectedOrg]); @@ -75,6 +78,7 @@ export const ManagePermissions: FunctionComponent = () = ['fieldsByKey', fromPermissionsState.fieldsByKey], ['objectPermissionMap', fromPermissionsState.objectPermissionMap], ['fieldPermissionMap', fromPermissionsState.fieldPermissionMap], + ['tabVisibilityPermissionMap', fromPermissionsState.tabVisibilityPermissionMap], ]} /> {location.pathname.endsWith('/editor') && !hasSelectionsMade ? : } diff --git a/apps/jetstream/src/app/components/manage-permissions/ManagePermissionsEditor.tsx b/apps/jetstream/src/app/components/manage-permissions/ManagePermissionsEditor.tsx index 60bfbcc5b..d3cac9b9a 100644 --- a/apps/jetstream/src/app/components/manage-permissions/ManagePermissionsEditor.tsx +++ b/apps/jetstream/src/app/components/manage-permissions/ManagePermissionsEditor.tsx @@ -25,6 +25,7 @@ import { RequireMetadataApiBanner } from '../core/RequireMetadataApiBanner'; import * as fromJetstreamEvents from '../core/jetstream-events'; import ManagePermissionsEditorFieldTable from './ManagePermissionsEditorFieldTable'; import ManagePermissionsEditorObjectTable from './ManagePermissionsEditorObjectTable'; +import ManagePermissionsEditorTabVisibilityTable from './ManagePermissionsEditorTabVisibilityTable'; import * as fromPermissionsState from './manage-permissions.state'; import { usePermissionRecords } from './usePermissionRecords'; import { generateExcelWorkbookFromTable } from './utils/permission-manager-export-utils'; @@ -32,12 +33,16 @@ import { getConfirmationModalContent, getDirtyFieldPermissions, getDirtyObjectPermissions, + getDirtyTabVisibilityPermissions, getFieldColumns, getFieldRows, getObjectColumns, getObjectRows, + getTabVisibilityColumns, + getTabVisibilityRows, updateFieldRowsAfterSave, updateObjectRowsAfterSave, + updateTabVisibilityRowsAfterSave, } from './utils/permission-manager-table-utils'; import { DirtyRow, @@ -49,20 +54,27 @@ import { PermissionFieldSaveData, PermissionObjectSaveData, PermissionSaveResults, + PermissionTabVisibilitySaveData, PermissionTableFieldCell, PermissionTableFieldCellPermission, PermissionTableObjectCell, PermissionTableObjectCellPermission, PermissionTableSummaryRow, + PermissionTableTabVisibilityCell, + PermissionTableTabVisibilityCellPermission, + TabVisibilityPermissionDefinitionMap, + TabVisibilityPermissionRecordForSave, } from './utils/permission-manager-types'; import { clearPermissionErrorMessage, collectProfileAndPermissionIds, getUpdatedFieldPermissions, getUpdatedObjectPermissions, + getUpdatedTabVisibilityPermissions, permissionsHaveError, prepareFieldPermissionSaveData, prepareObjectPermissionSaveData, + prepareTabVisibilityPermissionSaveData, savePermissionRecords, updatePermissionSetRecords, } from './utils/permission-manager-utils'; @@ -113,10 +125,13 @@ export const ManagePermissionsEditor: FunctionComponent>>({}); const [objectFilter, setObjectFilter] = useState(''); + const [tabVisibilityColumns, setTabVisibilityColumns] = useState< + ColumnWithFilter[] + >([]); + const [tabVisibilityRows, setTabVisibilityRows] = useState(null); + const [visibleTabVisibilityRows, setVisibleTabVisibilityRows] = useState(null); + const [dirtyTabVisibilityRows, setDirtyTabVisibilityRows] = useState>>({}); + const [tabVisibilityFilter, setTabVisibilityFilter] = useState(''); + const [fieldColumns, setFieldColumns] = useState[]>([]); const [fieldRows, setFieldRows] = useState(null); const [visibleFieldRows, setVisibleFieldRows] = useState(null); @@ -134,9 +157,11 @@ export const ManagePermissionsEditor: FunctionComponent(0); const [dirtyFieldCount, setDirtyFieldCount] = useState(0); + const [dirtyTabVisibilityCount, setDirtyTabVisibilityCount] = useState(0); const [objectsHaveErrors, setObjectsHaveErrors] = useState(false); const [fieldsHaveErrors, setFieldsHaveErrors] = useState(false); + const [tabVisibilityHaveErrors, setTabVisibilityHaveErrors] = useState(false); useEffect(() => { isMounted.current = true; @@ -150,6 +175,7 @@ export const ManagePermissionsEditor: FunctionComponent { - if (objectPermissionMap && fieldPermissionMap) { + if (objectPermissionMap && fieldPermissionMap && tabVisibilityPermissionMap) { setObjectsHaveErrors(permissionsHaveError(objectPermissionMap)); setFieldsHaveErrors(permissionsHaveError(fieldPermissionMap)); + setTabVisibilityHaveErrors(permissionsHaveError(tabVisibilityPermissionMap)); } - }, [objectPermissionMap, fieldPermissionMap]); + }, [objectPermissionMap, fieldPermissionMap, tabVisibilityPermissionMap]); useEffect(() => { setDirtyFieldCount(Object.values(dirtyFieldRows).reduce((output, { dirtyCount }) => output + dirtyCount, 0)); @@ -184,6 +211,10 @@ export const ManagePermissionsEditor: FunctionComponent output + dirtyCount, 0)); }, [dirtyObjectRows]); + useEffect(() => { + setDirtyTabVisibilityCount(Object.values(dirtyTabVisibilityRows).reduce((output, { dirtyCount }) => output + dirtyCount, 0)); + }, [dirtyTabVisibilityRows]); + useEffect(() => { if (fieldRows && fieldFilter) { setVisibleFieldRows(fieldRows.filter(multiWordObjectFilter(['label', 'apiName'], fieldFilter))); @@ -200,6 +231,14 @@ export const ManagePermissionsEditor: FunctionComponent { + if (tabVisibilityRows && tabVisibilityFilter) { + setVisibleTabVisibilityRows(tabVisibilityRows.filter(multiWordObjectFilter(['label', 'apiName'], tabVisibilityFilter))); + } else { + setVisibleTabVisibilityRows(tabVisibilityRows); + } + }, [tabVisibilityFilter, tabVisibilityRows]); + const handleObjectBulkRowUpdate = useCallback((rows: PermissionTableObjectCell[], indexes?: number[]) => { const rowsByKey = getMapOf(rows, 'key'); setObjectRows((prevRows) => (prevRows ? prevRows?.map((row) => rowsByKey[row.key] || row) : rows)); @@ -211,12 +250,7 @@ export const ManagePermissionsEditor: FunctionComponent { - output += createIsDirty ? 1 : 0; - output += readIsDirty ? 1 : 0; - output += editIsDirty ? 1 : 0; - output += deleteIsDirty ? 1 : 0; - output += viewAllIsDirty ? 1 : 0; - output += modifyAllIsDirty ? 1 : 0; + output += createIsDirty || readIsDirty || editIsDirty || deleteIsDirty || viewAllIsDirty || modifyAllIsDirty ? 1 : 0; return output; }, 0 @@ -243,8 +277,7 @@ export const ManagePermissionsEditor: FunctionComponent { - output += readIsDirty ? 1 : 0; - output += editIsDirty ? 1 : 0; + output += readIsDirty || editIsDirty ? 1 : 0; return output; }, 0); newValues[rowKey] = { rowKey, dirtyCount, row }; @@ -259,14 +292,41 @@ export const ManagePermissionsEditor: FunctionComponent { + const rowsByKey = getMapOf(rows, 'key'); + setTabVisibilityRows((prevRows) => (prevRows ? prevRows.map((row) => rowsByKey[row.key] || row) : rows)); + indexes = indexes || rows.map((row, index) => index); + setDirtyTabVisibilityRows((priorValue) => { + const newValues = { ...priorValue }; + indexes?.forEach((rowIndex) => { + const row = rows[rowIndex]; + const rowKey = row.key; // e.x. Obj__c.Field__c + const dirtyCount = Object.values(row.permissions).reduce((output, { availableIsDirty, visibleIsDirty }) => { + output += availableIsDirty || visibleIsDirty ? 1 : 0; + return output; + }, 0); + newValues[rowKey] = { rowKey, dirtyCount, row }; + }); + // remove items with a dirtyCount of 0 to reduce future processing required + return Object.keys(newValues).reduce((output: MapOf>, key) => { + if (newValues[key].dirtyCount) { + output[key] = newValues[key]; + } + return output; + }, {}); + }); + }, []); + function initTableData( includeColumns = true, objectPermissionMapOverride?: MapOf, - fieldPermissionMapOverride?: MapOf + fieldPermissionMapOverride?: MapOf, + tabVisibilityPermissionMapOverride?: MapOf ) { if (includeColumns) { setObjectColumns(getObjectColumns(selectedProfiles, selectedPermissionSets, profilesById, permissionSetsById)); setFieldColumns(getFieldColumns(selectedProfiles, selectedPermissionSets, profilesById, permissionSetsById)); + setTabVisibilityColumns(getTabVisibilityColumns(selectedProfiles, selectedPermissionSets, profilesById, permissionSetsById)); } const tempObjectRows = getObjectRows(selectedSObjects, objectPermissionMapOverride || objectPermissionMap || {}); setObjectRows(tempObjectRows); @@ -277,9 +337,16 @@ export const ManagePermissionsEditor: FunctionComponent 0) { + tabVisibilityPermissionData = prepareTabVisibilityPermissionSaveData(dirtyPermissions); + const ids = collectProfileAndPermissionIds(dirtyPermissions, profilesById, permissionSetsById); + profileIds = [...profileIds, ...ids.profileIds]; + permissionSetIds = [...permissionSetIds, ...ids.permissionSetIds]; + } + } let objectSaveResults: PermissionSaveResults[] | undefined = undefined; let fieldSaveResults: PermissionSaveResults[] | undefined = undefined; + let tabVisibilitySaveResults: + | PermissionSaveResults[] + | undefined = undefined; if (objectPermissionData) { objectSaveResults = await savePermissionRecords( @@ -353,6 +437,14 @@ export const ManagePermissionsEditor: FunctionComponent(selectedOrg, 'PermissionSetTabSetting', tabVisibilityPermissionData); + logger.log({ tabVisibilitySaveResults }); + } + // Update records so that SFDX is aware of the changes try { await updatePermissionSetRecords(selectedOrg, { @@ -378,6 +470,13 @@ export const ManagePermissionsEditor: FunctionComponent Reset Changes @@ -444,7 +554,7 @@ export const ManagePermissionsEditor: FunctionComponent Save @@ -495,6 +605,38 @@ export const ManagePermissionsEditor: FunctionComponent ), }, + + { + id: 'tab-visibility-permissions', + title: ( + + + + + Tab Visibility {dirtyTabVisibilityCount ? `(${dirtyTabVisibilityCount})` : ''} + + + ), + titleText: 'Tab Visibility', + disabled: true, + content: ( + + ), + }, + { id: 'field-permissions', title: ( diff --git a/apps/jetstream/src/app/components/manage-permissions/ManagePermissionsEditorFieldTable.tsx b/apps/jetstream/src/app/components/manage-permissions/ManagePermissionsEditorFieldTable.tsx index 108c9bb90..76d408e67 100644 --- a/apps/jetstream/src/app/components/manage-permissions/ManagePermissionsEditorFieldTable.tsx +++ b/apps/jetstream/src/app/components/manage-permissions/ManagePermissionsEditorFieldTable.tsx @@ -1,6 +1,6 @@ import { useNonInitialEffect } from '@jetstream/shared/ui-utils'; import { MapOf } from '@jetstream/types'; -import { AutoFullHeightContainer, ColumnWithFilter, DataTable } from '@jetstream/ui'; +import { AutoFullHeightContainer, ColumnWithFilter, DataTree } from '@jetstream/ui'; import groupBy from 'lodash/groupBy'; import { forwardRef, useCallback, useImperativeHandle, useState } from 'react'; import { RowHeightArgs } from 'react-data-grid'; @@ -69,7 +69,7 @@ export const ManagePermissionsEditorFieldTable = forwardRef - ) { - if (type === 'ROW') { - return 24; - } - return 34; -} - // summary row is just a placeholder for rendered content const SUMMARY_ROWS: PermissionTableSummaryRow[] = [{ type: 'HEADING' }, { type: 'ACTION' }]; @@ -39,9 +31,7 @@ export interface ManagePermissionsEditorObjectTableProps { export const ManagePermissionsEditorObjectTable = forwardRef( ({ columns, rows, totalCount, onFilter, onBulkUpdate, onDirtyRows }, ref) => { const [dirtyRows, setDirtyRows] = useState>>({}); - // const [expandedGroupIds, setExpandedGroupIds] = useState(() => new Set(rows.map((row) => row.sobject))); - // FIXME: figure out what we do and do not need here useImperativeHandle(ref, () => ({ resetChanges() { resetGridChanges({ rows, type: 'object' }); @@ -83,7 +73,7 @@ export const ManagePermissionsEditorObjectTable = forwardRef diff --git a/apps/jetstream/src/app/components/manage-permissions/ManagePermissionsEditorTabVisibilityTable.tsx b/apps/jetstream/src/app/components/manage-permissions/ManagePermissionsEditorTabVisibilityTable.tsx new file mode 100644 index 000000000..0a094ebe4 --- /dev/null +++ b/apps/jetstream/src/app/components/manage-permissions/ManagePermissionsEditorTabVisibilityTable.tsx @@ -0,0 +1,85 @@ +import { useNonInitialEffect } from '@jetstream/shared/ui-utils'; +import { MapOf } from '@jetstream/types'; +import { AutoFullHeightContainer, ColumnWithFilter, DataTable } from '@jetstream/ui'; +import { forwardRef, useCallback, useImperativeHandle, useState } from 'react'; +import { resetGridChanges, updateRowsFromColumnAction } from './utils/permission-manager-table-utils'; +import { + DirtyRow, + FieldPermissionTypes, + ManagePermissionsEditorTableRef, + PermissionManagerTableContext, + PermissionTableSummaryRow, + PermissionTableTabVisibilityCell, +} from './utils/permission-manager-types'; + +function getRowKey(row: PermissionTableTabVisibilityCell) { + return row.key; +} + +// summary row is just a placeholder for rendered content +const SUMMARY_ROWS: PermissionTableSummaryRow[] = [{ type: 'HEADING' }, { type: 'ACTION' }]; + +export interface ManagePermissionsEditorTabVisibilityTableProps { + columns: ColumnWithFilter[]; + rows: PermissionTableTabVisibilityCell[]; + totalCount: number; + onFilter: (value: string) => void; + onBulkUpdate: (rows: PermissionTableTabVisibilityCell[], indexes?: number[]) => void; + onDirtyRows?: (values: MapOf>) => void; +} + +export const ManagePermissionsEditorTabVisibilityTable = forwardRef( + ({ columns, rows, totalCount, onFilter, onBulkUpdate, onDirtyRows }, ref) => { + const [dirtyRows, setDirtyRows] = useState>>({}); + + useImperativeHandle(ref, () => ({ + resetChanges() { + resetGridChanges({ rows, type: 'tabVisibility' }); + setDirtyRows({}); + }, + })); + + useNonInitialEffect(() => { + dirtyRows && onDirtyRows && onDirtyRows(dirtyRows); + }, [dirtyRows, onDirtyRows]); + + function handleColumnAction(action: 'selectAll' | 'unselectAll' | 'reset', columnKey: string) { + const [id, typeLabel] = columnKey.split('-'); + onBulkUpdate(updateRowsFromColumnAction('tabVisibility', action, typeLabel as FieldPermissionTypes, id, rows)); + } + + const handleRowsChange = useCallback( + (rows: PermissionTableTabVisibilityCell[], { indexes }) => { + onBulkUpdate(rows, indexes); + }, + [onBulkUpdate] + ); + + return ( +
    + + + +
    + ); + } +); + +export default ManagePermissionsEditorTabVisibilityTable; diff --git a/apps/jetstream/src/app/components/manage-permissions/ManagePermissionsSelection.tsx b/apps/jetstream/src/app/components/manage-permissions/ManagePermissionsSelection.tsx index 6f02a8dee..3bbb516be 100644 --- a/apps/jetstream/src/app/components/manage-permissions/ManagePermissionsSelection.tsx +++ b/apps/jetstream/src/app/components/manage-permissions/ManagePermissionsSelection.tsx @@ -43,6 +43,7 @@ export const ManagePermissionsSelection: FunctionComponent({ key: 'permission-manager.sObjectsState', @@ -51,24 +55,6 @@ export const fieldsByKey = atom | null>({ default: null, }); -// // key = either Sobject name or field name with object prefix -// export const permissionsByObjectAndField = atom>({ -// key: 'permission-manager.permissionsByObjectAndField', -// default: null, -// }); - -// //KEY = {Id-SObjectName} ex: `${record.ParentId}-${record.Field}` -// export const objectPermissionsByKey = atom>({ -// key: 'permission-manager.objectPermissionsByKey', -// default: null, -// }); - -// //KEY = {Id-FieldName} ex: `${record.ParentId}-${record.Field}` -// export const fieldPermissionsByKey = atom>({ -// key: 'permission-manager.fieldPermissionsByKey', -// default: null, -// }); - export const objectPermissionMap = atom | null>({ key: 'permission-manager.objectPermissionMap', default: null, @@ -79,6 +65,11 @@ export const fieldPermissionMap = atom | nul default: null, }); +export const tabVisibilityPermissionMap = atom | null>({ + key: 'permission-manager.tabVisibilityPermissionMap', + default: null, +}); + /** * Returns true if all selections have been made */ diff --git a/apps/jetstream/src/app/components/manage-permissions/usePermissionRecords.tsx b/apps/jetstream/src/app/components/manage-permissions/usePermissionRecords.tsx index b1704056c..74a3ca9e2 100644 --- a/apps/jetstream/src/app/components/manage-permissions/usePermissionRecords.tsx +++ b/apps/jetstream/src/app/components/manage-permissions/usePermissionRecords.tsx @@ -1,14 +1,29 @@ import { logger } from '@jetstream/shared/client-logger'; import { queryAll, queryAllUsingOffset } from '@jetstream/shared/data'; import { useRollbar } from '@jetstream/shared/ui-utils'; -import { EntityParticlePermissionsRecord, FieldPermissionRecord, MapOf, ObjectPermissionRecord, SalesforceOrgUi } from '@jetstream/types'; +import { getMapOf } from '@jetstream/shared/utils'; +import { + EntityParticlePermissionsRecord, + FieldPermissionRecord, + MapOf, + ObjectPermissionRecord, + SalesforceOrgUi, + TabDefinitionRecord, + TabVisibilityPermissionRecord, +} from '@jetstream/types'; import { useCallback, useEffect, useRef, useState } from 'react'; -import { FieldPermissionDefinitionMap, ObjectPermissionDefinitionMap } from './utils/permission-manager-types'; +import { + FieldPermissionDefinitionMap, + ObjectPermissionDefinitionMap, + TabVisibilityPermissionDefinitionMap, +} from './utils/permission-manager-types'; import { getFieldDefinitionKey, getQueryForAllPermissionableFields, getQueryForFieldPermissions, getQueryObjectPermissions, + getQueryTabDefinition, + getQueryTabVisibilityPermissions, } from './utils/permission-manager-utils'; export function usePermissionRecords(selectedOrg: SalesforceOrgUi, sobjects: string[], profilePermSetIds: string[], permSetIds: string[]) { @@ -22,6 +37,7 @@ export function usePermissionRecords(selectedOrg: SalesforceOrgUi, sobjects: str const [objectPermissionMap, setObjectPermissionMap] = useState | null>(null); const [fieldPermissionMap, setFieldPermissionMap] = useState | null>(null); + const [tabVisibilityPermissionMap, setTabVisibilityPermissionMap] = useState | null>(null); useEffect(() => { isMounted.current = true; @@ -50,12 +66,26 @@ export function usePermissionRecords(selectedOrg: SalesforceOrgUi, sobjects: str queryAndCombineResults(selectedOrg, getQueryForAllPermissionableFields(sobjects), true, true), queryAndCombineResults(selectedOrg, getQueryObjectPermissions(sobjects, permSetIds, profilePermSetIds)), queryAndCombineResults(selectedOrg, getQueryForFieldPermissions(sobjects, permSetIds, profilePermSetIds)), - ]).then(([fieldDefinition, objectPermissions, fieldPermissions]) => { + queryAndCombineResults( + selectedOrg, + getQueryTabVisibilityPermissions(sobjects, permSetIds, profilePermSetIds) + ).then((record) => record.map((item) => ({ ...item, Name: item.Name.replace('standard-', '') }))), + queryAndCombineResults(selectedOrg, getQueryTabDefinition(sobjects), false, true).then((tabs) => + getMapOf(tabs, 'SobjectName') + ), + ]).then(([fieldDefinition, objectPermissions, fieldPermissions, tabVisibilityPermissions, tabDefinitions]) => { return { fieldsByObject: getAllFieldsByObject(fieldDefinition), fieldsByKey: groupFields(fieldDefinition), objectPermissionMap: getObjectPermissionMap(sobjects, profilePermSetIds, permSetIds, objectPermissions), fieldPermissionMap: getFieldPermissionMap(fieldDefinition, profilePermSetIds, permSetIds, fieldPermissions), + tabVisibilityPermissionMap: getTabVisibilityPermissionMap( + sobjects, + profilePermSetIds, + permSetIds, + tabVisibilityPermissions, + tabDefinitions + ), }; }); if (isMounted.current) { @@ -63,10 +93,11 @@ export function usePermissionRecords(selectedOrg: SalesforceOrgUi, sobjects: str setFieldsByKey(output.fieldsByKey); setObjectPermissionMap(output.objectPermissionMap); setFieldPermissionMap(output.fieldPermissionMap); + setTabVisibilityPermissionMap(output.tabVisibilityPermissionMap); } } catch (ex) { logger.warn('[useProfilesAndPermSets][ERROR]', ex.message); - rollbar.error('[useProfilesAndPermSets][ERROR]', ex); + rollbar.error('[useProfilesAndPermSets][ERROR]', { message: ex.message, stack: ex.stack }); if (isMounted.current) { setHasError(true); } @@ -85,6 +116,7 @@ export function usePermissionRecords(selectedOrg: SalesforceOrgUi, sobjects: str /** permissionsByObjectAndField, objectPermissionsByKey, fieldPermissionsByKey, */ objectPermissionMap, fieldPermissionMap, + tabVisibilityPermissionMap, hasError, }; } @@ -226,3 +258,54 @@ function getFieldPermissionMap( return output; }, {}); } + +function getTabVisibilityPermissionMap( + sobjects: string[], + selectedProfiles: string[], + selectedPermissionSets: string[], + permissions: TabVisibilityPermissionRecord[], + tabDefinitions: MapOf +): MapOf { + const objectPermissionsByFieldByParentId = permissions.reduce((output: MapOf>, item) => { + output[item.Name] = output[item.Name] || {}; + output[item.Name][item.ParentId] = item; + return output; + }, {}); + + return sobjects.reduce((output: MapOf, item) => { + const currItem: TabVisibilityPermissionDefinitionMap = { + apiName: item, + label: item, + metadata: item, + permissions: {}, + permissionKeys: [], + canSetPermission: !!tabDefinitions[item], + }; + + function processProfileAndPermSet(id: string) { + const permissionRecord = objectPermissionsByFieldByParentId[item]?.[id]; + currItem.permissionKeys.push(id); + if (permissionRecord) { + currItem.permissions[id] = { + available: true, + visible: permissionRecord.Visibility === 'DefaultOn' ? true : false, + record: permissionRecord, + canSetPermission: true, + }; + } else { + currItem.permissions[id] = { + available: false, + visible: false, + record: null, + canSetPermission: !!tabDefinitions[item], + }; + } + } + + selectedProfiles.forEach(processProfileAndPermSet); + selectedPermissionSets.forEach(processProfileAndPermSet); + + output[item] = currItem; + return output; + }, {}); +} diff --git a/apps/jetstream/src/app/components/manage-permissions/utils/permission-manager-export-utils.ts b/apps/jetstream/src/app/components/manage-permissions/utils/permission-manager-export-utils.ts index 3cdca26db..c78a03119 100644 --- a/apps/jetstream/src/app/components/manage-permissions/utils/permission-manager-export-utils.ts +++ b/apps/jetstream/src/app/components/manage-permissions/utils/permission-manager-export-utils.ts @@ -1,29 +1,38 @@ import { excelWorkbookToArrayBuffer, getMaxWidthFromColumnContent, initXlsx } from '@jetstream/shared/ui-utils'; import { ColumnWithFilter } from '@jetstream/ui'; import * as XLSX from 'xlsx'; -import { PermissionTableFieldCell, PermissionTableObjectCell, PermissionTableSummaryRow } from './permission-manager-types'; +import { + PermissionTableFieldCell, + PermissionTableObjectCell, + PermissionTableSummaryRow, + PermissionTableTabVisibilityCell, +} from './permission-manager-types'; initXlsx(XLSX); -type ObjectOrFieldColumn = +type ObjectOrFieldOrTabVisibilityColumn = | ColumnWithFilter - | ColumnWithFilter; + | ColumnWithFilter + | ColumnWithFilter; export function generateExcelWorkbookFromTable( - objectData: { columns: ObjectOrFieldColumn[]; rows: PermissionTableObjectCell[] }, - fieldData: { columns: ObjectOrFieldColumn[]; rows: PermissionTableFieldCell[] } + objectData: { columns: ObjectOrFieldOrTabVisibilityColumn[]; rows: PermissionTableObjectCell[] }, + tabVisibilityData: { columns: ObjectOrFieldOrTabVisibilityColumn[]; rows: PermissionTableTabVisibilityCell[] }, + fieldData: { columns: ObjectOrFieldOrTabVisibilityColumn[]; rows: PermissionTableFieldCell[] } ) { const workbook = XLSX.utils.book_new(); const objectWorksheet = generateObjectWorksheet(objectData.columns, objectData.rows); + const tabVisibilityWorksheet = generateTabVisibilityWorksheet(tabVisibilityData.columns, tabVisibilityData.rows); const fieldWorksheet = generateFieldWorksheet(fieldData.columns, fieldData.rows); XLSX.utils.book_append_sheet(workbook, objectWorksheet, 'Object Permissions'); + XLSX.utils.book_append_sheet(workbook, tabVisibilityWorksheet, 'Tab Visibility'); XLSX.utils.book_append_sheet(workbook, fieldWorksheet, 'Field Permissions'); return excelWorkbookToArrayBuffer(workbook); } -function generateObjectWorksheet(columns: ObjectOrFieldColumn[], rows: PermissionTableObjectCell[]) { +function generateObjectWorksheet(columns: ObjectOrFieldOrTabVisibilityColumn[], rows: PermissionTableObjectCell[]) { const merges: XLSX.Range[] = []; const header1: string[] = ['']; const header2: string[] = ['Object']; @@ -77,7 +86,7 @@ function generateObjectWorksheet(columns: ObjectOrFieldColumn[], rows: Permissio return worksheet; } -function generateFieldWorksheet(columns: ObjectOrFieldColumn[], rows: PermissionTableFieldCell[]) { +function generateFieldWorksheet(columns: ObjectOrFieldOrTabVisibilityColumn[], rows: PermissionTableFieldCell[]) { const merges: XLSX.Range[] = []; const header1: string[] = ['', '', '']; const header2: string[] = ['Object', 'Field Api Name', 'Field Label']; @@ -121,3 +130,46 @@ function generateFieldWorksheet(columns: ObjectOrFieldColumn[], rows: Permission worksheet['!merges'] = merges; return worksheet; } + +function generateTabVisibilityWorksheet(columns: ObjectOrFieldOrTabVisibilityColumn[], rows: PermissionTableTabVisibilityCell[]) { + const merges: XLSX.Range[] = []; + const header1: string[] = ['']; + const header2: string[] = ['Object']; + const excelRows = [header1, header2]; + + const permissionKeys: string[] = []; + + columns + .filter((col) => col.key?.endsWith('-available')) + .forEach((col) => { + // header 1 + header1.push(col.name as string); + header1.push(''); + // header1.push(''); + // merge the added cells + merges.push({ + s: { r: 0, c: header1.length - 2 }, + e: { r: 0, c: header1.length - 1 }, + }); + // header 2 + header2.push('Available'); + header2.push('Visible'); + // keep track of group order to ensure same across all rows + permissionKeys.push(col.key.split('-')[0]); + }); + + rows.forEach((row, i) => { + const currRow = [row.sobject]; + permissionKeys.forEach((key) => { + const permission = row.permissions[key]; + currRow.push(permission.available ? 'TRUE' : 'FALSE'); + currRow.push(permission.visible ? 'TRUE' : 'FALSE'); + }); + excelRows.push(currRow); + }); + + const worksheet = XLSX.utils.aoa_to_sheet(excelRows); + worksheet['!cols'] = getMaxWidthFromColumnContent(excelRows, new Set([0])); + worksheet['!merges'] = merges; + return worksheet; +} diff --git a/apps/jetstream/src/app/components/manage-permissions/utils/permission-manager-table-utils.tsx b/apps/jetstream/src/app/components/manage-permissions/utils/permission-manager-table-utils.tsx index 50f61d7b6..961554283 100644 --- a/apps/jetstream/src/app/components/manage-permissions/utils/permission-manager-table-utils.tsx +++ b/apps/jetstream/src/app/components/manage-permissions/utils/permission-manager-table-utils.tsx @@ -17,8 +17,8 @@ import { setColumnFromType, } from '@jetstream/ui'; import startCase from 'lodash/startCase'; -import { Fragment, FunctionComponent, useContext, useRef, useState } from 'react'; -import { FormatterProps, SummaryFormatterProps } from 'react-data-grid'; +import { Fragment, FunctionComponent, useContext, useMemo, useRef, useState } from 'react'; +import { RenderCellProps, RenderSummaryCellProps } from 'react-data-grid'; import { BulkActionCheckbox, DirtyRow, @@ -29,14 +29,45 @@ import { ObjectPermissionItem, ObjectPermissionTypes, PermissionManagerTableContext, + PermissionTableCellExtended, PermissionTableFieldCell, PermissionTableFieldCellPermission, PermissionTableObjectCell, PermissionTableObjectCellPermission, PermissionTableSummaryRow, + PermissionTableTabVisibilityCell, + PermissionTableTabVisibilityCellPermission, PermissionType, + PermissionTypes, + TabVisibilityPermissionDefinitionMap, + TabVisibilityPermissionItem, + TabVisibilityPermissionTypes, } from './permission-manager-types'; +type PermissionTypeColumn = T extends 'object' + ? ColumnWithFilter + : T extends 'field' + ? ColumnWithFilter + : T extends 'tabVisibility' + ? ColumnWithFilter + : never; + +type PermissionActionType = T extends 'object' + ? 'Create' | 'Read' | 'Edit' | 'Delete' | 'ViewAll' | 'ModifyAll' + : T extends 'field' + ? 'Read' | 'Edit' + : T extends 'tabVisibility' + ? 'Available' | 'Visible' + : never; + +type PermissionActionAction = T extends 'object' + ? ObjectPermissionTypes + : T extends 'field' + ? FieldPermissionTypes + : T extends 'tabVisibility' + ? TabVisibilityPermissionTypes + : never; + function setObjectValue(which: ObjectPermissionTypes, row: PermissionTableObjectCell, permissionId: string, value: boolean) { const newRow = { ...row, permissions: { ...row.permissions, [permissionId]: { ...row.permissions[permissionId] } } }; const permission = newRow.permissions[permissionId]; @@ -75,6 +106,24 @@ function setFieldValue(which: FieldPermissionTypes, row: PermissionTableFieldCel return newRow; } +function setTabVisibilityValue( + which: TabVisibilityPermissionTypes, + row: PermissionTableTabVisibilityCell, + permissionId: string, + value: boolean +) { + const newRow = { ...row, permissions: { ...row.permissions, [permissionId]: { ...row.permissions[permissionId] } } }; + const permission = newRow.permissions[permissionId]; + if (which === 'available') { + permission.available = value; + setTabVisibilityDependencies(permission, value, [], ['visible']); + } else if (which === 'visible') { + permission.visible = value; + setTabVisibilityDependencies(permission, value, ['available'], []); + } + return newRow; +} + /** * Set dependent fields based on what selections are made */ @@ -115,11 +164,33 @@ function setFieldDependencies( permission.editIsDirty = permission.edit !== permission.record.edit; } -export function resetGridChanges(options: { rows: PermissionTableFieldCell[] | PermissionTableObjectCell[]; type: PermissionType }); +function setTabVisibilityDependencies( + permission: PermissionTableTabVisibilityCellPermission, + value: boolean, + setIfTrue: TabVisibilityPermissionTypes[], + setIfFalse: TabVisibilityPermissionTypes[] +) { + if (value) { + setIfTrue.forEach((prop) => (permission[prop] = value)); + } else { + setIfFalse.forEach((prop) => (permission[prop] = value)); + } + permission.availableIsDirty = permission.available !== permission.record.available; + permission.visibleIsDirty = permission.visible !== permission.record.visible; +} + +export function resetGridChanges(options: { + rows: PermissionTableFieldCell[] | PermissionTableObjectCell[] | PermissionTableTabVisibilityCell[]; + type: PermissionType; +}); +// eslint-disable-next-line no-redeclare export function resetGridChanges({ rows, type, -}: { rows: PermissionTableObjectCell[]; type: 'object' } | { rows: PermissionTableFieldCell[]; type: 'field' }) { +}: + | { rows: PermissionTableObjectCell[]; type: 'object' } + | { rows: PermissionTableFieldCell[]; type: 'field' } + | { rows: PermissionTableTabVisibilityCell[]; type: 'tabVisibility' }) { if (type === 'object') { return rows.map((row) => { row = { ...row }; @@ -149,7 +220,7 @@ export function resetGridChanges({ }); return row; }); - } else { + } else if (type === 'field') { return rows.map((row) => { Object.keys(row.permissions).forEach((permissionKey) => { let permission = row.permissions[permissionKey]; @@ -164,6 +235,21 @@ export function resetGridChanges({ }); return row; }); + } else if (type === 'tabVisibility') { + return rows.map((row) => { + Object.keys(row.permissions).forEach((permissionKey) => { + let permission = row.permissions[permissionKey]; + if (permission.visibleIsDirty) { + permission = { ...permission }; + row.permissions[permissionKey] = permission; + permission.available = permission.availableIsDirty ? !permission.available : permission.available; + permission.visible = permission.visibleIsDirty ? !permission.visible : permission.visible; + permission.availableIsDirty = false; + permission.visibleIsDirty = false; + } + }); + return row; + }); } } @@ -187,6 +273,12 @@ export function getDirtyFieldPermissions(dirtyRows: MapOf>) { + return Object.values(dirtyRows).flatMap(({ row }) => + Object.values(row.permissions).filter((permission) => permission.availableIsDirty || permission.visibleIsDirty) + ); +} + export function getObjectColumns( selectedProfiles: string[], selectedPermissionSets: string[], @@ -205,7 +297,7 @@ export function getObjectColumns( return data && `${data.label} (${data.apiName})`; }, summaryCellClass: 'bg-color-gray-dark no-outline', - summaryFormatter: ({ row }) => { + renderSummaryCell: ({ row }) => { if (row.type === 'HEADING') { return ; } else if (row.type === 'ACTION') { @@ -220,9 +312,9 @@ export function getObjectColumns( width: 100, resizable: false, frozen: true, - formatter: RowActionRenderer, + renderCell: RowActionRenderer, summaryCellClass: ({ type }) => (type === 'HEADING' ? 'bg-color-gray' : null), - summaryFormatter: ({ row }) => { + renderSummaryCell: ({ row }) => { if (row.type === 'ACTION') { return ; } @@ -278,9 +370,7 @@ export function getObjectRows(selectedSObjects: string[], objectPermissionMap: M apiName: objectPermission.apiName, label: objectPermission.label, tableLabel: `${objectPermission.label} (${objectPermission.apiName})`, - // FIXME: are there circumstances where it should be read-only? - // // formula fields and auto-number fields do not allow editing - allowEditPermission: true, // objectPermission.metadata.IsCompound || objectPermission.metadata.IsCreatable, + allowEditPermission: true, permissions: {}, }; @@ -353,7 +443,7 @@ export function getFieldColumns( width: 85, cellClass: 'bg-color-gray-dark', summaryCellClass: 'bg-color-gray-dark', - groupFormatter: ({ isExpanded }) => ( + renderGroupCell: ({ isExpanded }) => ( ( + renderGroupCell: ({ groupKey, childRows, toggleGroup }) => ( <> - @@ -1144,7 +1497,7 @@ export const BulkActionRenderer = () => {

    This change will apply to{' '} - {formatNumber(rows.length)} {pluralizeFromNumber(type, rows.length)} + {formatNumber(rowCount)} {pluralizeFromNumber(type, rowCount)} {' '} and all selected profiles and permission sets

    diff --git a/apps/jetstream/src/app/components/manage-permissions/utils/permission-manager-types.ts b/apps/jetstream/src/app/components/manage-permissions/utils/permission-manager-types.ts index c55013364..3d45385dd 100644 --- a/apps/jetstream/src/app/components/manage-permissions/utils/permission-manager-types.ts +++ b/apps/jetstream/src/app/components/manage-permissions/utils/permission-manager-types.ts @@ -6,22 +6,28 @@ import { ObjectPermissionRecord, RecordAttributes, RecordResult, + TabVisibilityPermissionRecord, } from '@jetstream/types'; -export type PermissionType = 'object' | 'field'; +export type PermissionType = 'object' | 'field' | 'tabVisibility'; export type ObjectPermissionTypes = 'create' | 'read' | 'edit' | 'delete' | 'viewAll' | 'modifyAll'; export type FieldPermissionTypes = 'read' | 'edit'; +export type TabVisibilityPermissionTypes = 'available' | 'visible'; + +export type PermissionTypes = ObjectPermissionTypes | FieldPermissionTypes | TabVisibilityPermissionTypes; export type BulkActionCheckbox = { - id: ObjectPermissionTypes; + id: PermissionTypes; label: string; value: boolean; disabled: boolean; }; +export type PermissionDefinitionMap = ObjectPermissionDefinitionMap | FieldPermissionDefinitionMap | TabVisibilityPermissionDefinitionMap; + export interface ObjectPermissionDefinitionMap { apiName: string; - label: string; // TODO: ;( + label: string; metadata: string; // FIXME: this should probably be Describe metadata // used to retain order of permissions permissionKeys: string[]; // this is permission set ids, which could apply to profile or perm set @@ -55,6 +61,31 @@ export interface FieldPermissionItem { errorMessage?: Maybe; } +export interface TabVisibilityPermissionDefinitionMap { + apiName: string; + label: string; + metadata: string; + /** + * False if a tab does not exist for this object + */ + canSetPermission: boolean; + // used to retain order of permissions + permissionKeys: string[]; // this is permission set ids, which could apply to profile or perm set + permissions: MapOf; +} + +export interface TabVisibilityPermissionItem { + available: boolean; + visible: boolean; + record?: Maybe; + /** + * False if a tab does not exist for this object + * In which case, permissions cannot be set + */ + canSetPermission: boolean; + errorMessage?: string; +} + export interface ObjectPermissionRecordForSave extends Omit { attributes: Partial; Id?: Maybe; @@ -65,11 +96,18 @@ export interface FieldPermissionRecordForSave extends Omit { + attributes: Partial; + Id?: string; + Name?: string; + ParentId?: string; +} + export interface PermissionSaveResults { dirtyPermission: DirtyPermType; dirtyPermissionIdx: number; - operation: 'insert' | 'update'; - record: RecordType; + operation: 'insert' | 'update' | 'delete'; + record?: RecordType; recordIdx: number; response?: Maybe; } @@ -83,6 +121,8 @@ export interface PermissionTableCell; } +export type PermissionTableCellExtended = PermissionTableObjectCell | PermissionTableFieldCell | PermissionTableTabVisibilityCell; + export interface PermissionTableObjectCell extends PermissionTableCell { allowEditPermission: boolean; // TODO: what other permissions may be restricted here?? } @@ -92,11 +132,15 @@ export interface PermissionTableFieldCell extends PermissionTableCell { + canSetPermission: boolean; +} + export interface PermissionTableSummaryRow { type: 'HEADING' | 'ACTION'; } -export interface PermissionTableObjectCellPermissionBase { +export interface PermissionTableObjectCellPermissionBase { rowKey: string; parentId: string; // permissions set (placeholder profile or permission set Id) sobject: string; @@ -126,6 +170,18 @@ export interface PermissionTableFieldCellPermission extends PermissionTableObjec editIsDirty: boolean; } +export interface PermissionTableTabVisibilityCellPermission extends PermissionTableObjectCellPermissionBase { + available: boolean; + availableIsDirty: boolean; + visible: boolean; + visibleIsDirty: boolean; +} + +export type PermissionTableCellPermission = + | PermissionTableObjectCellPermission + | PermissionTableFieldCellPermission + | PermissionTableTabVisibilityCellPermission; + export interface ManagePermissionsEditorTableProps { fieldsByObject: MapOf; } @@ -144,20 +200,29 @@ export interface PermissionObjectSaveData { permissionSaveResults: PermissionSaveResults[]; recordsToInsert: ObjectPermissionRecordForSave[]; recordsToUpdate: ObjectPermissionRecordForSave[]; + recordsToDelete: string[]; } export interface PermissionFieldSaveData { permissionSaveResults: PermissionSaveResults[]; recordsToInsert: FieldPermissionRecordForSave[]; recordsToUpdate: FieldPermissionRecordForSave[]; + recordsToDelete: string[]; +} + +export interface PermissionTabVisibilitySaveData { + permissionSaveResults: PermissionSaveResults[]; + recordsToInsert: TabVisibilityPermissionRecordForSave[]; + recordsToUpdate: TabVisibilityPermissionRecordForSave[]; + recordsToDelete: string[]; } export interface PermissionManagerTableContext { type: PermissionType; - rows: (PermissionTableObjectCell | PermissionTableFieldCell)[]; + rows: PermissionTableCellExtended[]; totalCount: number; onFilterRows: (value: string) => void; onRowAction: (action: 'selectAll' | 'unselectAll' | 'reset', columnKey: string) => void; onColumnAction: (action: 'selectAll' | 'unselectAll' | 'reset', columnKey: string) => void; - onBulkAction: (rows: (PermissionTableObjectCell | PermissionTableFieldCell)[]) => void; + onBulkAction: (rows: PermissionTableCellExtended[]) => void; } diff --git a/apps/jetstream/src/app/components/manage-permissions/utils/permission-manager-utils.ts b/apps/jetstream/src/app/components/manage-permissions/utils/permission-manager-utils.ts index 2eea1926b..33cab3cd2 100644 --- a/apps/jetstream/src/app/components/manage-permissions/utils/permission-manager-utils.ts +++ b/apps/jetstream/src/app/components/manage-permissions/utils/permission-manager-utils.ts @@ -11,19 +11,26 @@ import { PermissionSetWithProfileRecord, RecordResult, SalesforceOrgUi, + TabVisibilityPermissionRecord, } from '@jetstream/types'; import type { DescribeGlobalSObjectResult } from 'jsforce'; -import { composeQuery, getField, Query, WhereClause } from 'soql-parser-js'; +import { Query, WhereClause, composeQuery, getField } from 'soql-parser-js'; import { FieldPermissionDefinitionMap, FieldPermissionRecordForSave, ObjectPermissionDefinitionMap, ObjectPermissionRecordForSave, + PermissionDefinitionMap, PermissionFieldSaveData, PermissionObjectSaveData, PermissionSaveResults, + PermissionTabVisibilitySaveData, + PermissionTableCellPermission, PermissionTableFieldCellPermission, PermissionTableObjectCellPermission, + PermissionTableTabVisibilityCellPermission, + TabVisibilityPermissionDefinitionMap, + TabVisibilityPermissionRecordForSave, } from './permission-manager-types'; const MAX_OBJ_IN_QUERY = 100; @@ -58,6 +65,7 @@ export function prepareObjectPermissionSaveData(dirtyPermissions: PermissionTabl permissionSaveResults: PermissionSaveResults[]; recordsToInsert: ObjectPermissionRecordForSave[]; recordsToUpdate: ObjectPermissionRecordForSave[]; + recordsToDelete: string[]; }, perm, i @@ -93,7 +101,7 @@ export function prepareObjectPermissionSaveData(dirtyPermissions: PermissionTabl return output; }, - { permissionSaveResults: [], recordsToInsert: [], recordsToUpdate: [] } + { permissionSaveResults: [], recordsToInsert: [], recordsToUpdate: [], recordsToDelete: [] } ); } @@ -105,6 +113,7 @@ export function prepareFieldPermissionSaveData(dirtyPermissions: PermissionTable permissionSaveResults: PermissionSaveResults[]; recordsToInsert: FieldPermissionRecordForSave[]; recordsToUpdate: FieldPermissionRecordForSave[]; + recordsToDelete: string[]; }, perm, i @@ -137,22 +146,80 @@ export function prepareFieldPermissionSaveData(dirtyPermissions: PermissionTable return output; }, - { permissionSaveResults: [], recordsToInsert: [], recordsToUpdate: [] } + { permissionSaveResults: [], recordsToInsert: [], recordsToUpdate: [], recordsToDelete: [] } + ); +} + +export function prepareTabVisibilityPermissionSaveData( + dirtyPermissions: PermissionTableTabVisibilityCellPermission[] +): PermissionTabVisibilitySaveData { + return dirtyPermissions.reduce( + ( + output: { + // used to easily keep track of the input data with the actual results + permissionSaveResults: PermissionSaveResults[]; + recordsToInsert: TabVisibilityPermissionRecordForSave[]; + recordsToUpdate: TabVisibilityPermissionRecordForSave[]; + recordsToDelete: string[]; + }, + perm, + i + ) => { + let newRecord: TabVisibilityPermissionRecordForSave | undefined = undefined; + let recordIdx: number; + let operation: 'insert' | 'update' | 'delete' = 'insert'; + if (perm.record.record && !perm.available) { + output.recordsToDelete.push(perm.record.record.Id); + recordIdx = output.recordsToDelete.length - 1; + operation = 'delete'; + } else if (perm.record.record) { + newRecord = { + attributes: { type: 'PermissionSetTabSetting' }, + Id: perm.record.record.Id, + Visibility: perm.visible ? 'DefaultOn' : 'DefaultOff', + }; + output.recordsToUpdate.push(newRecord); + recordIdx = output.recordsToUpdate.length - 1; + operation = 'update'; + } else { + newRecord = { + attributes: { type: 'PermissionSetTabSetting' }, + Name: perm.sobject.endsWith('__c') ? perm.sobject : `standard-${perm.sobject}`, + Visibility: perm.visible ? 'DefaultOn' : 'DefaultOff', + ParentId: perm.parentId, + }; + output.recordsToInsert.push(newRecord); + recordIdx = output.recordsToInsert.length - 1; + } + + output.permissionSaveResults.push({ + dirtyPermission: perm, + dirtyPermissionIdx: i, + operation, + record: newRecord, + recordIdx, + }); + + return output; + }, + { permissionSaveResults: [], recordsToInsert: [], recordsToUpdate: [], recordsToDelete: [] } ); } export async function savePermissionRecords( org: SalesforceOrgUi, - type: 'ObjectPermissions' | 'FieldPermissions', + type: 'ObjectPermissions' | 'FieldPermissions' | 'PermissionSetTabSetting', preparedData: { permissionSaveResults: PermissionSaveResults[]; recordsToInsert: RecordType[]; recordsToUpdate: RecordType[]; + recordsToDelete: string[]; } ): Promise[]> { - const { permissionSaveResults, recordsToInsert, recordsToUpdate } = preparedData; + const { permissionSaveResults, recordsToInsert, recordsToUpdate, recordsToDelete } = preparedData; let recordInsertResults: RecordResult[] = []; let recordUpdateResults: RecordResult[] = []; + let recordDeleteResults: RecordResult[] = []; if (recordsToInsert.length) { recordInsertResults = ( await Promise.all( @@ -167,12 +234,21 @@ export async function savePermissionRecords( ) ).flat(); } + if (recordsToDelete.length) { + recordDeleteResults = ( + await Promise.all( + splitArrayToMaxSize(recordsToDelete, 200).map((ids) => sobjectOperation(org, type, 'delete', { ids }, { allOrNone: false })) + ) + ).flat(); + } permissionSaveResults.forEach((saveResults) => { if (saveResults.operation === 'insert') { saveResults.response = recordInsertResults[saveResults.recordIdx]; - } else { + } else if (saveResults.operation === 'update') { saveResults.response = recordUpdateResults[saveResults.recordIdx]; + } else if (saveResults.operation === 'delete') { + saveResults.response = recordDeleteResults[saveResults.recordIdx]; } }); @@ -209,7 +285,7 @@ export async function updatePermissionSetRecords( } export function collectProfileAndPermissionIds( - dirtyPermissions: (PermissionTableObjectCellPermission | PermissionTableFieldCellPermission)[], + dirtyPermissions: PermissionTableCellPermission[], profilesById: MapOf, permissionSetsById: MapOf ) { @@ -408,9 +484,88 @@ export function getUpdatedFieldPermissions( return output; } -export function clearPermissionErrorMessage( - permissionMap: MapOf -): MapOf { +export function getUpdatedTabVisibilityPermissions( + objectPermissionMap: MapOf, + permissionSaveResults: PermissionSaveResults[] +) { + const output: MapOf = { ...objectPermissionMap }; + // remove all error messages across all objects + Object.keys(output).forEach((key) => { + output[key] = { ...output[key] }; + output[key].permissionKeys.forEach((permKey) => { + output[key].permissions = { + ...output[key].permissions, + [permKey]: { + ...output[key].permissions[permKey], + errorMessage: undefined, + }, + }; + }); + }); + + permissionSaveResults.forEach(({ dirtyPermission, operation, response }) => { + const fieldKey = dirtyPermission.sobject; + if (!isErrorResponse(response)) { + const fieldPermission: Partial = { + Id: response?.id, + ParentId: dirtyPermission.parentId, + Visibility: dirtyPermission.visible ? 'DefaultOn' : 'DefaultOff', + Name: dirtyPermission.sobject, + // missing Parent related lookup, as we do not have data for it + }; + if (operation === 'insert') { + output[fieldKey] = { + ...output[fieldKey], + permissions: { + ...output[fieldKey].permissions, + [dirtyPermission.parentId]: { + ...output[fieldKey].permissions[dirtyPermission.parentId], + available: dirtyPermission.available, + visible: dirtyPermission.visible, + record: fieldPermission as TabVisibilityPermissionRecord, + }, + }, + }; + } else { + const isDelete = !dirtyPermission.available; + output[fieldKey] = { + ...output[fieldKey], + permissions: { + ...output[fieldKey].permissions, + [dirtyPermission.parentId]: { + ...output[fieldKey].permissions[dirtyPermission.parentId], + available: dirtyPermission.available, + visible: dirtyPermission.visible, + record: isDelete ? null : (fieldPermission as TabVisibilityPermissionRecord), + }, + }, + }; + } + } else { + logger.warn('[SAVE ERROR]', { dirtyPermission, response }); + output[fieldKey] = { + ...output[fieldKey], + permissions: { + ...output[fieldKey].permissions, + [dirtyPermission.parentId]: { + ...output[fieldKey].permissions[dirtyPermission.parentId], + errorMessage: response.errors + .map((err) => + // (field not detectable in advance): Field Name: bad value for restricted picklist field: X.X + err.statusCode === 'INVALID_OR_NULL_FOR_RESTRICTED_PICKLIST' + ? 'Salesforce does not allow modification of permissions for this field.' + : err.message + ) + .join('\n'), + }, + }, + }; + } + }); + return output; +} + +export function clearPermissionErrorMessage(permissionMap: MapOf): MapOf { return Object.keys(permissionMap).reduce((output: MapOf, key) => { output[key] = { ...permissionMap[key] }; output[key].permissions = { ...output[key].permissions }; @@ -421,9 +576,7 @@ export function clearPermissionErrorMessage( - permissionMap: MapOf -): boolean { +export function permissionsHaveError(permissionMap: MapOf): boolean { return Object.values(permissionMap).some((item) => Object.values(item.permissions).some((permission) => permission.errorMessage)); } @@ -567,6 +720,70 @@ export function getQueryForFieldPermissions(allSobjects: string[], profilePermSe return queries; } +/** + * Gets query object permissions + * @param allSobjects + * @param permSetIds + * @param profilePermSetIds + * @returns query object permissions + */ +export function getQueryTabVisibilityPermissions(allSobjects: string[], permSetIds: string[], profilePermSetIds: string[]): string[] { + const queries = splitArrayToMaxSize(allSobjects, MAX_OBJ_IN_QUERY).map((sobjects) => { + const query: Query = { + fields: [ + getField('Id'), + getField('Name'), + getField('Visibility'), + getField('ParentId'), + getField('Parent.Id'), + getField('Parent.Name'), + getField('Parent.IsOwnedByProfile'), + getField('Parent.ProfileId'), + ], + sObject: 'PermissionSetTabSetting', + where: getWhereClauseForPermissionQuery( + sobjects.map((sobject) => (sobject.endsWith('__c') ? sobject : `standard-${sobject}`)), + permSetIds, + profilePermSetIds, + 'Name' + ), + orderBy: { + field: 'Name', + order: 'ASC', + }, + }; + + return composeQuery(query); + }); + logger.log('getQueryTabVisibilityPermissions()', queries); + return queries; +} + +export function getQueryTabDefinition(allSobjects: string[]): string[] { + const queries = splitArrayToMaxSize(allSobjects, MAX_OBJ_IN_QUERY).map((sobjects) => { + const query: Query = { + fields: [getField('Id'), getField('Name'), getField('Label'), getField('SobjectName')], + sObject: 'TabDefinition', + where: { + left: { + field: 'SobjectName', + operator: 'IN', + value: sobjects, + literalType: 'STRING', + }, + }, + orderBy: { + field: 'SobjectName', + order: 'ASC', + }, + }; + + return composeQuery(query); + }); + logger.log('getQueryTabDefinition()', queries); + return queries; +} + /** * Build WHERE clause for object/field permissions * Assumptions: @@ -577,13 +794,18 @@ export function getQueryForFieldPermissions(allSobjects: string[], profilePermSe * @param permSetIds * @param profilePermSetIds */ -function getWhereClauseForPermissionQuery(sobjects: string[], profilePermSetIds: string[], permSetIds: string[]): WhereClause | undefined { +function getWhereClauseForPermissionQuery( + sobjects: string[], + profilePermSetIds: string[], + permSetIds: string[], + sobjectNameField: 'SobjectType' | 'Name' = 'SobjectType' +): WhereClause | undefined { if (!sobjects.length || (!permSetIds.length && !profilePermSetIds.length)) { return undefined; } return { left: { - field: 'SobjectType', + field: sobjectNameField, operator: 'IN', value: sobjects, literalType: 'STRING', diff --git a/apps/jetstream/src/app/components/orgs/AddOrg.tsx b/apps/jetstream/src/app/components/orgs/AddOrg.tsx index b577048e6..cccb91974 100644 --- a/apps/jetstream/src/app/components/orgs/AddOrg.tsx +++ b/apps/jetstream/src/app/components/orgs/AddOrg.tsx @@ -116,7 +116,9 @@ export const AddOrg: FunctionComponent = ({ className, label = 'Add className="slds-input" placeholder="org-domain" value={customUrl} - onChange={(event) => setCustomUrl(event.target.value)} + onChange={(event) => + setCustomUrl((prevValue) => (event.target.value || '').replaceAll(/(https:\/\/)|(\.my\.salesforce\.com)/g, '')) + } /> )} diff --git a/apps/jetstream/src/app/components/orgs/OrgInfoPopover.tsx b/apps/jetstream/src/app/components/orgs/OrgInfoPopover.tsx index 1302b2510..a2b32e454 100644 --- a/apps/jetstream/src/app/components/orgs/OrgInfoPopover.tsx +++ b/apps/jetstream/src/app/components/orgs/OrgInfoPopover.tsx @@ -4,8 +4,8 @@ import { SalesforceOrgUi } from '@jetstream/types'; import { ButtonGroupContainer, Checkbox, - ColorSwatches, ColorSwatchItem, + ColorSwatches, Grid, GridCol, Icon, @@ -17,8 +17,8 @@ import { import classNames from 'classnames'; import startCase from 'lodash/startCase'; import { Fragment, FunctionComponent, ReactNode, useEffect, useState } from 'react'; -import { useRecoilState } from 'recoil'; -import { applicationCookieState } from '../../app-state'; +import { useRecoilValue } from 'recoil'; +import { applicationCookieState, selectSkipFrontdoorAuth } from '../../app-state'; const EMPTY_COLOR = '_none_'; @@ -49,7 +49,7 @@ export interface OrgInfoPopoverProps { onUpdateOrg: (org: SalesforceOrgUi, updatedOrg: Partial) => void; } -function getOrgProp(serverUrl: string, org: SalesforceOrgUi, prop: keyof SalesforceOrgUi, label?: string) { +function getOrgProp(serverUrl: string, org: SalesforceOrgUi, skipFrontDoorAuth: boolean, prop: keyof SalesforceOrgUi, label?: string) { label = label || startCase(prop); let value: string | number | boolean | ReactNode = org[prop]; let tooltip = ''; @@ -59,14 +59,20 @@ function getOrgProp(serverUrl: string, org: SalesforceOrgUi, prop: keyof Salesfo if (prop === 'organizationId') { tooltip = String(value); value = ( - + {value} ); } else if (prop === 'userId') { tooltip = String(value); value = ( - + {value} ); @@ -96,7 +102,8 @@ export const OrgInfoPopover: FunctionComponent = ({ onRemoveOrg, onUpdateOrg, }) => { - const [applicationState] = useRecoilState(applicationCookieState); + const { serverUrl } = useRecoilValue(applicationCookieState); + const skipFrontDoorAuth = useRecoilValue(selectSkipFrontdoorAuth); const [orgLabel, setOrgLabel] = useState(org.label || org.username); const [orgColor, setOrgColor] = useState(org.color || EMPTY_COLOR); const [removeOrgActive, setRemoveOrgActive] = useState(false); @@ -117,7 +124,7 @@ export const OrgInfoPopover: FunctionComponent = ({ }, [org]); function handleFixOrg() { - addOrg({ serverUrl: applicationState.serverUrl, loginUrl: org.instanceUrl }, (addedOrg: SalesforceOrgUi) => { + addOrg({ serverUrl: serverUrl, loginUrl: org.instanceUrl }, (addedOrg: SalesforceOrgUi) => { onAddOrg(addedOrg, true); }); } @@ -197,7 +204,8 @@ export const OrgInfoPopover: FunctionComponent = ({
    = ({ Home Page = ({ - {getOrgProp(applicationState.serverUrl, org, 'orgName', 'Org Name')} - {getOrgProp(applicationState.serverUrl, org, 'organizationId', 'Org Id')} - {getOrgProp(applicationState.serverUrl, org, 'orgInstanceName', 'Instance')} - {getOrgProp(applicationState.serverUrl, org, 'instanceUrl')} - {getOrgProp(applicationState.serverUrl, org, 'orgOrganizationType', 'Org Type')} - {getOrgProp(applicationState.serverUrl, org, 'orgIsSandbox', 'Is Sandbox')} - {getOrgProp(applicationState.serverUrl, org, 'orgTrialExpirationDate', 'Trial Expiration')} - {getOrgProp(applicationState.serverUrl, org, 'userId')} - {getOrgProp(applicationState.serverUrl, org, 'username')} - {getOrgProp(applicationState.serverUrl, org, 'email')} + {getOrgProp(serverUrl, org, skipFrontDoorAuth, 'orgName', 'Org Name')} + {getOrgProp(serverUrl, org, skipFrontDoorAuth, 'organizationId', 'Org Id')} + {getOrgProp(serverUrl, org, skipFrontDoorAuth, 'orgInstanceName', 'Instance')} + {getOrgProp(serverUrl, org, skipFrontDoorAuth, 'instanceUrl')} + {getOrgProp(serverUrl, org, skipFrontDoorAuth, 'orgOrganizationType', 'Org Type')} + {getOrgProp(serverUrl, org, skipFrontDoorAuth, 'orgIsSandbox', 'Is Sandbox')} + {getOrgProp(serverUrl, org, skipFrontDoorAuth, 'orgTrialExpirationDate', 'Trial Expiration')} + {getOrgProp(serverUrl, org, skipFrontDoorAuth, 'userId')} + {getOrgProp(serverUrl, org, skipFrontDoorAuth, 'username')} + {getOrgProp(serverUrl, org, skipFrontDoorAuth, 'email')}
    diff --git a/apps/jetstream/src/app/components/orgs/OrgSelectionRequired.tsx b/apps/jetstream/src/app/components/orgs/OrgSelectionRequired.tsx index bfb9ff69a..bc907a1c2 100644 --- a/apps/jetstream/src/app/components/orgs/OrgSelectionRequired.tsx +++ b/apps/jetstream/src/app/components/orgs/OrgSelectionRequired.tsx @@ -2,11 +2,10 @@ import { css } from '@emotion/react'; import { logger } from '@jetstream/shared/client-logger'; import { checkOrgHealth, getOrgs } from '@jetstream/shared/data'; import { SalesforceOrgUi } from '@jetstream/types'; -import { Alert, EmptyState, Icon, NoAccess2Illustration } from '@jetstream/ui'; +import { Alert, EmptyState, Icon, NoAccess2Illustration, fireToast } from '@jetstream/ui'; import { Fragment, FunctionComponent, useCallback, useState } from 'react'; import { useRecoilValue, useSetRecoilState } from 'recoil'; import * as fromAppState from '../../app-state'; -import { fireToast } from '../core/AppToast'; import * as fromJetstreamEvents from '../core/jetstream-events'; import AddOrg from './AddOrg'; import { OrgWelcomeInstructions } from './OrgWelcomeInstructions'; diff --git a/apps/jetstream/src/app/components/platform-event-monitor/PlatformEventMonitor.tsx b/apps/jetstream/src/app/components/platform-event-monitor/PlatformEventMonitor.tsx index 9ba9dc4cc..124b35fed 100644 --- a/apps/jetstream/src/app/components/platform-event-monitor/PlatformEventMonitor.tsx +++ b/apps/jetstream/src/app/components/platform-event-monitor/PlatformEventMonitor.tsx @@ -1,16 +1,16 @@ import { css } from '@emotion/react'; import { TITLES } from '@jetstream/shared/constants'; -import { useTitle } from '@jetstream/shared/ui-utils'; +import { useNonInitialEffect, useTitle } from '@jetstream/shared/ui-utils'; import { SplitWrapper as Split } from '@jetstream/splitjs'; -import { ListItem, SalesforceOrgUi } from '@jetstream/types'; +import { ListItem, ListItemGroup, SalesforceOrgUi } from '@jetstream/types'; import { AutoFullHeightContainer } from '@jetstream/ui'; -import type { DescribeGlobalSObjectResult } from 'jsforce'; import { FunctionComponent, useEffect, useRef, useState } from 'react'; import { useRecoilState, useRecoilValue } from 'recoil'; import { applicationCookieState, selectedOrgState } from '../../app-state'; import PlatformEventMonitorFetchEventStatus from './PlatformEventMonitorFetchEventStatus'; import PlatformEventMonitorListenerCard from './PlatformEventMonitorListenerCard'; import PlatformEventMonitorPublisherCard from './PlatformEventMonitorPublisherCard'; +import { PlatformEventObject } from './platform-event-monitor.types'; import { usePlatformEvent } from './usePlatformEvent'; // eslint-disable-next-line @typescript-eslint/no-empty-interface @@ -32,8 +32,9 @@ export const PlatformEventMonitor: FunctionComponent subscribe, unsubscribe, } = usePlatformEvent({ selectedOrg }); - const [platformEventsList, setPlatformEventsList] = useState[]>([]); - const [subscribedPlatformEventsList, setSubscribedPlatformEventsList] = useState[]>([]); + const [platformEventsListSubscriptions, setPlatformEventsListSubscriptions] = useState[]>([]); + const [platformEventsListPublisher, setPlatformEventsListPublisher] = useState[]>([]); + const [subscribedPlatformEventsList, setSubscribedPlatformEventsList] = useState[]>([]); const [picklistKey, setPicklistKey] = useState(1); const [selectedSubscribeEvent, setSelectedSubscribeEvent] = useState(null); const [selectedPublishEvent, setSelectedPublishEvent] = useState(null); @@ -45,30 +46,88 @@ export const PlatformEventMonitor: FunctionComponent }; }, []); + useNonInitialEffect(() => { + setSelectedPublishEvent(null); + }, [selectedOrg]); + useEffect(() => { - const events = platformEvents.map>((event) => ({ - id: event.name, - label: event.label, - secondaryLabel: event.name, - value: event.name, - meta: event, - })); - if (events.length) { - setPlatformEventsList(events); - setSelectedSubscribeEvent(events[0].id); - setSelectedPublishEvent(events[0].id); + const subscriptionEvents: ListItemGroup[] = [ + { + id: 'PLATFORM_EVENT', + label: 'Platform Events (Custom)', + items: platformEvents + .filter((item) => item.type === 'PLATFORM_EVENT') + .map>((event) => ({ + id: event.channel, + label: `${event.label} (${event.name})`, + secondaryLabel: event.channel, + secondaryLabelOnNewLine: true, + value: event.channel, + meta: event, + })), + }, + { + id: 'PLATFORM_EVENT_STANDARD', + label: 'Platform Events (Standard)', + items: platformEvents + .filter((item) => item.type === 'PLATFORM_EVENT_STANDARD') + .map>((event) => ({ + id: event.channel, + label: `${event.label} (${event.name})`, + secondaryLabel: event.channel, + secondaryLabelOnNewLine: true, + value: event.channel, + meta: event, + })), + }, + { + id: 'CHANGE_EVENT', + label: 'Change Data Capture Events', + items: platformEvents + .filter((item) => item.type === 'CHANGE_EVENT') + .map>((event) => ({ + id: event.channel, + label: `${event.label} (${event.name})`, + secondaryLabel: event.channel, + secondaryLabelOnNewLine: true, + value: event.channel, + meta: event, + })), + }, + ]; + + setPlatformEventsListSubscriptions(subscriptionEvents); + setSelectedSubscribeEvent(platformEvents.length ? platformEvents[0].channel : null); + setPicklistKey((prevKey) => prevKey + 1); + + const publisherEvents = platformEvents + .filter((event) => event.type === 'PLATFORM_EVENT') + .map>((event) => ({ + id: event.name, + label: event.label, + secondaryLabel: event.name, + secondaryLabelOnNewLine: true, + value: event.name, + meta: event, + })); + + if (publisherEvents.length) { + setPlatformEventsListPublisher(publisherEvents); + setSelectedPublishEvent(publisherEvents[0].id); setPicklistKey((prevKey) => prevKey + 1); } }, [platformEvents]); useEffect(() => { - setSubscribedPlatformEventsList(platformEventsList.filter((item) => !!messagesByChannel[item.value])); - }, [messagesByChannel]); + setSubscribedPlatformEventsList( + platformEventsListSubscriptions.flatMap((item) => item.items).filter((item) => !!messagesByChannel[item.value]) + ); + }, [messagesByChannel, platformEventsListSubscriptions]); const hasErrorOrNoEvents = !hasPlatformEvents || platformEventFetchError; return ( - + {hasErrorOrNoEvents && ( serverUrl={serverUrl} loadingPlatformEvents={loadingPlatformEvents} picklistKey={picklistKey} - platformEventsList={platformEventsList} + platformEventsList={platformEventsListPublisher} selectedPublishEvent={selectedPublishEvent} onSelectedPublishEvent={setSelectedPublishEvent} publish={publish} diff --git a/apps/jetstream/src/app/components/platform-event-monitor/PlatformEventMonitorEvents.tsx b/apps/jetstream/src/app/components/platform-event-monitor/PlatformEventMonitorEvents.tsx index 5427e2920..e2bffabff 100644 --- a/apps/jetstream/src/app/components/platform-event-monitor/PlatformEventMonitorEvents.tsx +++ b/apps/jetstream/src/app/components/platform-event-monitor/PlatformEventMonitorEvents.tsx @@ -1,12 +1,21 @@ import { css } from '@emotion/react'; import { orderStringsBy } from '@jetstream/shared/utils'; -import { AutoFullHeightContainer, ColumnWithFilter, DataTable } from '@jetstream/ui'; +import { AutoFullHeightContainer, ColumnWithFilter, ContextMenuActionData, ContextMenuItem, DataTree } from '@jetstream/ui'; +import copyToClipboard from 'copy-to-clipboard'; import groupBy from 'lodash/groupBy'; -import { FunctionComponent, useEffect, useState } from 'react'; -import { FormatterProps, RowHeightArgs } from 'react-data-grid'; +import { FunctionComponent, useCallback, useEffect, useState } from 'react'; +import { RenderCellProps, RowHeightArgs } from 'react-data-grid'; import { MessagesByChannel } from './usePlatformEvent'; -export const WrappedTextFormatter: FunctionComponent> = ({ column, row }) => { +type ContextEventAction = 'COPY_CELL' | 'COPY_EVENT' | 'COPY_EVENT_ALL'; + +const TABLE_CONTEXT_MENU_ITEMS: ContextMenuItem[] = [ + { label: 'Copy cell to clipboard', value: 'COPY_CELL' }, + { label: 'Copy event to clipboard', value: 'COPY_EVENT' }, + { label: 'Copy all events to clipboard', value: 'COPY_EVENT_ALL' }, +]; + +export const WrappedTextFormatter: FunctionComponent> = ({ column, row }) => { const value = row[column.key]; return (

    [] = [ name: 'Event', key: 'event', width: 230, - // rowGroup: true, - // hide: true, - // lockVisible: true, - // lockPosition: true, - // tooltipField: 'event', frozen: true, }, { name: 'Payload', key: 'payload', width: 450, - // wrapText: true, - // autoHeight: true, - formatter: WrappedTextFormatter, + renderCell: WrappedTextFormatter, cellClass: 'break-all', + draggable: true, }, { name: 'UUID', key: 'uuid', width: 160, - // tooltipField: 'uuid', + draggable: true, }, { name: 'Replay Id', key: 'replayId', width: 120, - // tooltipField: 'replayId', + draggable: true, }, ]; const groupedRows = ['event'] as const; function getRowId(data: PlatformEventRow): string { - return data.uuid; + return data.uuid || `${data.replayId}` || JSON.stringify(data); } function getRowHeight({ row, type }: RowHeightArgs) { @@ -105,10 +108,26 @@ export const PlatformEventMonitorEvents: FunctionComponent, data: ContextMenuActionData) => { + switch (item.value) { + case 'COPY_CELL': + copyToClipboard(JSON.stringify(data.row[data.column.key], null, 2), { format: 'text/plain' }); + break; + case 'COPY_EVENT': + copyToClipboard(JSON.stringify(data.row, null, 2), { format: 'text/plain' }); + break; + case 'COPY_EVENT_ALL': + copyToClipboard(JSON.stringify(data.rows, null, 2), { format: 'text/plain' }); + break; + } + }, + [] + ); + return ( - setExpandedGroupIds(items)} rowHeight={getRowHeight} + contextMenuItems={TABLE_CONTEXT_MENU_ITEMS} + contextMenuAction={handleContextMenuAction} /> ); diff --git a/apps/jetstream/src/app/components/platform-event-monitor/PlatformEventMonitorFetchEventStatus.tsx b/apps/jetstream/src/app/components/platform-event-monitor/PlatformEventMonitorFetchEventStatus.tsx index 1ee682be9..5219bbeab 100644 --- a/apps/jetstream/src/app/components/platform-event-monitor/PlatformEventMonitorFetchEventStatus.tsx +++ b/apps/jetstream/src/app/components/platform-event-monitor/PlatformEventMonitorFetchEventStatus.tsx @@ -29,6 +29,7 @@ export const PlatformEventMonitorFetchEventStatus: FunctionComponent[]; - subscribedPlatformEventsList: ListItem[]; + platformEventsList: ListItemGroup[]; + subscribedPlatformEventsList: ListItem[]; selectedSubscribeEvent?: Maybe; messagesByChannel: MessagesByChannel; fetchPlatformEvents: (clearCache?: boolean) => void; @@ -34,6 +34,7 @@ export const PlatformEventMonitorListenerCard: FunctionComponent

    Subscribe to Events
    diff --git a/apps/jetstream/src/app/components/platform-event-monitor/PlatformEventMonitorPublisherCard.tsx b/apps/jetstream/src/app/components/platform-event-monitor/PlatformEventMonitorPublisherCard.tsx index 0f8c29a66..b15f92416 100644 --- a/apps/jetstream/src/app/components/platform-event-monitor/PlatformEventMonitorPublisherCard.tsx +++ b/apps/jetstream/src/app/components/platform-event-monitor/PlatformEventMonitorPublisherCard.tsx @@ -4,15 +4,16 @@ import { useReducerFetchFn } from '@jetstream/shared/ui-utils'; import { ListItem, Maybe, PicklistFieldValues, Record, SalesforceOrgUi } from '@jetstream/types'; import { Card, ComboboxWithItems, Grid, Icon, ScopedNotification, Spinner, Tooltip } from '@jetstream/ui'; import { formatRelative } from 'date-fns'; -import type { DescribeGlobalSObjectResult, DescribeSObjectResult } from 'jsforce'; +import type { DescribeSObjectResult } from 'jsforce'; import { Fragment, FunctionComponent, useCallback, useEffect, useReducer, useRef, useState } from 'react'; +import { PlatformEventObject } from './platform-event-monitor.types'; export interface PlatformEventMonitorPublisherCardProps { selectedOrg: SalesforceOrgUi; serverUrl: string; loadingPlatformEvents: boolean; picklistKey: string | number; - platformEventsList: ListItem[]; + platformEventsList: ListItem[]; selectedPublishEvent: Maybe; onSelectedPublishEvent: (id: string) => void; publish: (platformEventName: string, data: any) => Promise; @@ -139,6 +140,7 @@ export const PlatformEventMonitorPublisherCard: FunctionComponent[]; + platformEventsList: ListItemGroup[]; selectedSubscribeEvent?: Maybe; messagesByChannel: MessagesByChannel; fetchPlatformEvents: (clearCache?: boolean) => void; - subscribe: (platformEventName: string, replayId?: number) => Promise; - unsubscribe: (platformEventName: string) => Promise; + subscribe: (channel: string, replayId?: number) => Promise; + unsubscribe: (channel: string) => Promise; onSelectedSubscribeEvent: (id: string) => void; } @@ -65,12 +65,14 @@ export const PlatformEventMonitorSubscribe: FunctionComponent
    - onSelectedSubscribeEvent(item.id)} /> diff --git a/apps/jetstream/src/app/components/platform-event-monitor/platform-event-monitor.types.ts b/apps/jetstream/src/app/components/platform-event-monitor/platform-event-monitor.types.ts new file mode 100644 index 000000000..070e94bd7 --- /dev/null +++ b/apps/jetstream/src/app/components/platform-event-monitor/platform-event-monitor.types.ts @@ -0,0 +1,25 @@ +export interface PlatformEventObject { + name: string; + label: string; + channel: string; + type: 'PLATFORM_EVENT' | 'PLATFORM_EVENT_STANDARD' | 'CHANGE_EVENT'; +} + +export interface EventMessage { + clientId: string; + channel: string; + id: string; + successful: boolean; +} + +export interface EventMessageUnsuccessful extends EventMessage { + subscription?: string; + error?: string; + successful: false; + failure?: { + connectionType: string; + exception?: string; + reason?: string; + transport?: string; + }; +} diff --git a/apps/jetstream/src/app/components/platform-event-monitor/platform-event-monitor.utils.ts b/apps/jetstream/src/app/components/platform-event-monitor/platform-event-monitor.utils.ts index ddef8a26a..17d53c683 100644 --- a/apps/jetstream/src/app/components/platform-event-monitor/platform-event-monitor.utils.ts +++ b/apps/jetstream/src/app/components/platform-event-monitor/platform-event-monitor.utils.ts @@ -1,12 +1,9 @@ import { logger } from '@jetstream/shared/client-logger'; import { HTTP } from '@jetstream/shared/constants'; -import { MapOf, Maybe, SalesforceOrgUi } from '@jetstream/types'; -import { CometD, Message, Extension, SubscriptionHandle } from 'cometd'; +import { MapOf, SalesforceOrgUi } from '@jetstream/types'; +import { CometD, Extension, Message, SubscriptionHandle } from 'cometd'; import isNumber from 'lodash/isNumber'; - -/** - * TODO: a worker might be the best solution here to ensure events are received from background - */ +import { EventMessage, EventMessageUnsuccessful } from './platform-event-monitor.types'; const subscriptions = new Map(); @@ -17,10 +14,12 @@ export function init({ serverUrl, defaultApiVersion, selectedOrg, + onSubscribeError, }: { serverUrl: string; defaultApiVersion: string; selectedOrg: SalesforceOrgUi; + onSubscribeError?: (message: EventMessageUnsuccessful) => void; }) { return new Promise((resolve, reject) => { const cometd = new CometD(); @@ -56,14 +55,17 @@ export function init({ } }); - cometd.addListener('/meta/connect', (message) => { + cometd.addListener('/meta/connect', (message: EventMessage) => { logger.log('[COMETD] connect', message); }); - cometd.addListener('/meta/disconnect', (message) => { + cometd.addListener('/meta/disconnect', (message: EventMessage) => { logger.log('[COMETD] disconnect', message); }); - cometd.addListener('/meta/unsuccessful', (message) => { + // Library appears to have incorrect type for subscription property + cometd.addListener('/meta/unsuccessful', (message: unknown) => { logger.log('[COMETD] unsuccessful', message); + // message.subscription -> not valid + onSubscribeError && onSubscribeError(message as EventMessageUnsuccessful); }); (cometd as any).onListenerException = (exception, subscriptionHandle, isListener, message) => { logger.warn('[COMETD][LISTENER][ERROR]', exception?.message, message, subscriptionHandle); @@ -72,23 +74,21 @@ export function init({ } export function subscribe( - { cometd, platformEventName, replayId }: { cometd: CometD; platformEventName: string; replayId?: number }, + { cometd, channel, replayId }: { cometd: CometD; channel: string; replayId?: number }, callback: (message: T) => void ) { - const channel = `/event/${platformEventName}`; - if (!cometd.isDisconnected()) { const replayExt = cometd.getExtension(CometdReplayExtension.EXT_NAME) as any; if (replayExt) { - replayExt.addChannel(platformEventName, replayId); + replayExt.addChannel(channel, replayId); } - if (subscriptions.has(platformEventName)) { + if (subscriptions.has(channel)) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - cometd.unsubscribe(subscriptions.get(platformEventName)!); + cometd.unsubscribe(subscriptions.get(channel)!); } subscriptions.set( - platformEventName, + channel, cometd.subscribe(channel, (message) => { callback(message as any); }) @@ -96,12 +96,12 @@ export function subscribe( } } -export function unsubscribe({ cometd, platformEventName }: { cometd: CometD; platformEventName: string }) { +export function unsubscribe({ cometd, channel }: { cometd: CometD; channel: string }) { if (!cometd.isDisconnected()) { - if (subscriptions.has(platformEventName)) { + if (subscriptions.has(channel)) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - cometd.unsubscribe(subscriptions.get(platformEventName)!); - subscriptions.delete(platformEventName); + cometd.unsubscribe(subscriptions.get(channel)!); + subscriptions.delete(channel); } // if no more subscriptions, disconnect everything if (!subscriptions.size) { @@ -135,12 +135,10 @@ class CometdReplayExtension implements Extension { } addChannel(channel: string, replay?: number) { - channel = channel.startsWith('/event') ? channel : `/event/${channel}`; this.replayFromMap[channel] = replay ?? -1; } removeChannel(channel: string) { - channel = channel.startsWith('/event') ? channel : `/event/${channel}`; this.replayFromMap[channel] = undefined; } diff --git a/apps/jetstream/src/app/components/platform-event-monitor/usePlatformEvent.ts b/apps/jetstream/src/app/components/platform-event-monitor/usePlatformEvent.ts index d39e0f5d7..cac679447 100644 --- a/apps/jetstream/src/app/components/platform-event-monitor/usePlatformEvent.ts +++ b/apps/jetstream/src/app/components/platform-event-monitor/usePlatformEvent.ts @@ -2,7 +2,6 @@ import { logger } from '@jetstream/shared/client-logger'; import { ANALYTICS_KEYS } from '@jetstream/shared/constants'; import { clearCacheForOrg, describeGlobal, sobjectOperation } from '@jetstream/shared/data'; import { useDebounce, useRollbar } from '@jetstream/shared/ui-utils'; -import { orderObjectsBy } from '@jetstream/shared/utils'; import { MapOf, Maybe, @@ -11,13 +10,14 @@ import { PlatformEventMessagePayload, SalesforceOrgUi, } from '@jetstream/types'; +import { fireToast } from '@jetstream/ui'; import { CometD } from 'cometd'; -import type { DescribeGlobalSObjectResult } from 'jsforce'; +import orderBy from 'lodash/orderBy'; import { useCallback, useEffect, useRef, useState } from 'react'; import { useRecoilState } from 'recoil'; import { applicationCookieState } from '../../app-state'; import { useAmplitude } from '../core/analytics'; -import { fireToast } from '../core/AppToast'; +import { EventMessageUnsuccessful, PlatformEventObject } from './platform-event-monitor.types'; import * as platformEventUtils from './platform-event-monitor.utils'; export type MessagesByChannel = MapOf<{ replayId?: number; messages: PlatformEventMessagePayload[] }>; @@ -25,7 +25,7 @@ export type MessagesByChannel = MapOf<{ replayId?: number; messages: PlatformEve export function usePlatformEvent({ selectedOrg }: { selectedOrg: SalesforceOrgUi }): { hasPlatformEvents: boolean; platformEventFetchError?: Maybe; - platformEvents: DescribeGlobalSObjectResult[]; + platformEvents: PlatformEventObject[]; messagesByChannel: MessagesByChannel; loadingPlatformEvents: boolean; fetchPlatformEvents: (clearCache?: boolean) => void; @@ -38,7 +38,7 @@ export function usePlatformEvent({ selectedOrg }: { selectedOrg: SalesforceOrgUi const rollbar = useRollbar(); const { trackEvent } = useAmplitude(); const [{ serverUrl, defaultApiVersion }] = useRecoilState(applicationCookieState); - const [platformEvents, setPlatformEvents] = useState([]); + const [platformEvents, setPlatformEvents] = useState([]); const [loadingPlatformEvents, setPlatformLoadingEvents] = useState(false); const [hasPlatformEvents, setHasPlatformEvents] = useState(true); const [platformEventFetchError, setPlatformEventFetchError] = useState>(null); @@ -79,9 +79,19 @@ export function usePlatformEvent({ selectedOrg }: { selectedOrg: SalesforceOrgUi } setPlatformLoadingEvents(true); setPlatformEventFetchError(null); - const platformEvents = orderObjectsBy( - (await describeGlobal(selectedOrg)).data.sobjects.filter((obj) => obj.name.endsWith('__e')), - 'name' + const platformEvents = orderBy( + (await describeGlobal(selectedOrg)).data.sobjects + .filter((obj) => (obj.name.endsWith('__e') || obj.name.endsWith('Event')) && !obj.queryable) + .map(({ name, label }): PlatformEventObject => { + return { + name, + label, + channel: name.endsWith('ChangeEvent') ? `/data/${name}` : `/event/${name}`, + type: name.endsWith('ChangeEvent') ? 'CHANGE_EVENT' : name.endsWith('__e') ? 'PLATFORM_EVENT' : 'PLATFORM_EVENT_STANDARD', + }; + }), + [(obj) => obj.name.endsWith('__e'), (obj) => obj.name.endsWith('ChangeEvent'), 'label'], + ['desc', 'asc', 'asc'] ); if (isMounted.current) { setPlatformEvents(platformEvents); @@ -113,25 +123,49 @@ export function usePlatformEvent({ selectedOrg }: { selectedOrg: SalesforceOrgUi [] ); + const handleSubscribeError = useCallback((message: EventMessageUnsuccessful) => { + logger.warn('[PLATFORM EVENT][ERROR]', message); + if (message.subscription) { + fireToast({ type: 'error', message: `Error subscribing to event: ${message.subscription}. ${message.error}` }); + setMessagesByChannel((item) => { + return Object.keys(item) + .filter((key) => key !== message.subscription) + .reduce((output: MessagesByChannel, key) => { + output[key] = item[key]; + return output; + }, {}); + }); + } else if (message.channel === '/meta/handshake') { + fireToast({ type: 'error', message: `Error subscribing to event: ${message.failure?.reason || 'Unknown reason'}.` }); + } else { + fireToast({ type: 'error', message: `There was an unknown error subscribing to the event` }); + } + }, []); + const subscribe = useCallback( - async (platformEventName: string, replayId?: number) => { + async (channel: string, replayId?: number) => { try { if (selectedOrg) { let requiredInit = false; if (!cometD.current || cometD.current.isDisconnected()) { - cometD.current = await platformEventUtils.init({ defaultApiVersion, selectedOrg, serverUrl }); + cometD.current = await platformEventUtils.init({ + defaultApiVersion, + selectedOrg, + serverUrl, + onSubscribeError: handleSubscribeError, + }); requiredInit = true; } const cometd = cometD.current; - platformEventUtils.subscribe({ cometd, platformEventName, replayId }, onEvent(replayId)); + platformEventUtils.subscribe({ cometd, channel, replayId }, onEvent(replayId)); setMessagesByChannel((item) => { item = { ...item }; - item[platformEventName] = { messages: [], replayId }; + item[channel] = { messages: [], replayId }; return item; }); - trackEvent(ANALYTICS_KEYS.platform_event_subscribed, { requiredInit }); + trackEvent(ANALYTICS_KEYS.platform_event_subscribed, { requiredInit, channel, replayId }); } } catch (ex) { logger.warn('[PLATFORM EVENT][ERROR]', ex.message); @@ -142,15 +176,15 @@ export function usePlatformEvent({ selectedOrg }: { selectedOrg: SalesforceOrgUi ); const unsubscribe = useCallback( - async (platformEventName: string) => { + async (channel: string) => { try { if (cometD.current) { const cometd = cometD.current; - platformEventUtils.unsubscribe({ cometd, platformEventName }); + platformEventUtils.unsubscribe({ cometd, channel }); setMessagesByChannel((item) => { return Object.keys(item) - .filter((key) => key !== platformEventName) + .filter((key) => key !== channel) .reduce((output: MessagesByChannel, key) => { output[key] = item[key]; return output; diff --git a/apps/jetstream/src/app/components/query/QueryBuilder/ExecuteQueryButton.tsx b/apps/jetstream/src/app/components/query/QueryBuilder/ExecuteQueryButton.tsx index 13c2d8c9e..62a76cf4d 100644 --- a/apps/jetstream/src/app/components/query/QueryBuilder/ExecuteQueryButton.tsx +++ b/apps/jetstream/src/app/components/query/QueryBuilder/ExecuteQueryButton.tsx @@ -1,7 +1,7 @@ import { Maybe } from '@jetstream/types'; -import { Icon } from '@jetstream/ui'; +import { Icon, KeyboardShortcut, Tooltip, getModifierKey } from '@jetstream/ui'; import type { DescribeGlobalSObjectResult } from 'jsforce'; -import React, { Fragment, FunctionComponent } from 'react'; +import { FunctionComponent } from 'react'; import { Link } from 'react-router-dom'; interface ExecuteQueryButtonProps { @@ -12,25 +12,33 @@ interface ExecuteQueryButtonProps { export const ExecuteQueryButton: FunctionComponent = ({ soql, isTooling, selectedSObject }) => { return ( - + <> {soql && selectedSObject && ( - + +
    + } > - - Execute - + + + Execute + + )} {!soql && ( )} - + ); }; diff --git a/apps/jetstream/src/app/components/query/QueryBuilder/QueryBuilder.tsx b/apps/jetstream/src/app/components/query/QueryBuilder/QueryBuilder.tsx index f3cde614f..ad26fc387 100644 --- a/apps/jetstream/src/app/components/query/QueryBuilder/QueryBuilder.tsx +++ b/apps/jetstream/src/app/components/query/QueryBuilder/QueryBuilder.tsx @@ -40,6 +40,7 @@ import QueryHistory, { QueryHistoryRef } from '../QueryHistory/QueryHistory'; import { QueryHistoryType } from '../QueryHistory/query-history.state'; import ManualSoql from '../QueryOptions/ManualSoql'; import QueryBuilderAdvancedOptions from '../QueryOptions/QueryBuilderAdvancedOptions'; +import QueryCount from '../QueryOptions/QueryCount'; import QueryFilter from '../QueryOptions/QueryFilter'; import QueryLimit from '../QueryOptions/QueryLimit'; import QueryOrderBy from '../QueryOptions/QueryOrderBy'; @@ -309,7 +310,7 @@ export const QueryBuilder: FunctionComponent = () => { - + @@ -427,6 +428,7 @@ export const QueryBuilder: FunctionComponent = () => { content: ( = () => { }, { id: 'soql', - title: 'Soql Query', + title: ( + + Soql Query + + + ), + titleText: 'SOQL Query', content: ( = () => { ), }, ]} - allowMultiple > ), }, diff --git a/apps/jetstream/src/app/components/query/QueryBuilder/QueryBuilderSoqlUpdater.tsx b/apps/jetstream/src/app/components/query/QueryBuilder/QueryBuilderSoqlUpdater.tsx index 7377d9d9b..97ae6d78c 100644 --- a/apps/jetstream/src/app/components/query/QueryBuilder/QueryBuilderSoqlUpdater.tsx +++ b/apps/jetstream/src/app/components/query/QueryBuilder/QueryBuilderSoqlUpdater.tsx @@ -1,5 +1,5 @@ import { FunctionComponent, useEffect } from 'react'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; import { GroupByFieldClause, GroupByFnClause, Query } from 'soql-parser-js'; import * as fromQueryState from '../query.state'; import { composeSoqlQuery } from '../utils/query-utils'; @@ -19,6 +19,7 @@ export const QueryBuilderSoqlUpdater: FunctionComponent = () => { const queryLimit = useRecoilValue(fromQueryState.selectQueryLimit); const queryLimitSkip = useRecoilValue(fromQueryState.selectQueryLimitSkip); const [soql, setSoql] = useRecoilState(fromQueryState.querySoqlState); + const setSoqlCount = useSetRecoilState(fromQueryState.querySoqlCountState); useEffect(() => { if (!!selectedSObject && selectedFields?.length > 0) { @@ -32,18 +33,12 @@ export const QueryBuilderSoqlUpdater: FunctionComponent = () => { limit: queryLimit, offset: queryLimitSkip, }; + const queryCount: Query = { ...query, fields: [{ type: 'FieldFunctionExpression', functionName: 'COUNT', parameters: [] }] }; setSoql(composeSoqlQuery(query, filters, hasGroupBy ? havingClauses : undefined)); - // if (queryWorker) { - // queryWorker.postMessage({ - // name: 'composeQuery', - // data: { - // query: query, - // whereExpression: debouncedFilters, - // }, - // }); - // } + setSoqlCount(composeSoqlQuery(queryCount, filters, hasGroupBy ? havingClauses : undefined)); } else if (soql !== '') { setSoql(''); + setSoqlCount(''); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedSObject, selectedFields, filters, havingClauses, groupByClauses, orderByClauses, queryLimit, queryLimitSkip]); diff --git a/apps/jetstream/src/app/components/query/QueryBuilder/QueryChildFields.tsx b/apps/jetstream/src/app/components/query/QueryBuilder/QueryChildFields.tsx index b2f5393c9..b89bbfdd6 100644 --- a/apps/jetstream/src/app/components/query/QueryBuilder/QueryChildFields.tsx +++ b/apps/jetstream/src/app/components/query/QueryBuilder/QueryChildFields.tsx @@ -97,7 +97,7 @@ export const QueryChildFields: FunctionComponent = ({ (field): QueryFieldWithPolymorphic => ({ field: `${basePath}${field}`, polymorphicObj: queryField.isPolymorphic ? queryField.sobject : undefined, - metadata: queryField.fields[field].metadata, + metadata: queryField.fields[field]?.metadata, }) ); }); diff --git a/apps/jetstream/src/app/components/query/QueryBuilder/QuerySObjects.tsx b/apps/jetstream/src/app/components/query/QueryBuilder/QuerySObjects.tsx index 95e937727..3c50e9a21 100644 --- a/apps/jetstream/src/app/components/query/QueryBuilder/QuerySObjects.tsx +++ b/apps/jetstream/src/app/components/query/QueryBuilder/QuerySObjects.tsx @@ -1,7 +1,7 @@ import { SalesforceOrgUi } from '@jetstream/types'; import { ConnectedSobjectList } from '@jetstream/ui'; import type { DescribeGlobalSObjectResult } from 'jsforce'; -import React, { Fragment, FunctionComponent } from 'react'; +import { FunctionComponent } from 'react'; import { useRecoilValue, useResetRecoilState } from 'recoil'; import { selectedOrgState } from '../../../app-state'; import * as fromQueryState from '../query.state'; @@ -49,16 +49,14 @@ export const QuerySObjects: FunctionComponent = ({ } return ( - - - + ); }; diff --git a/apps/jetstream/src/app/components/query/QueryBuilder/QuerySubquerySObjects.tsx b/apps/jetstream/src/app/components/query/QueryBuilder/QuerySubquerySObjects.tsx index 5cdc6843c..b2ab59fbf 100644 --- a/apps/jetstream/src/app/components/query/QueryBuilder/QuerySubquerySObjects.tsx +++ b/apps/jetstream/src/app/components/query/QueryBuilder/QuerySubquerySObjects.tsx @@ -3,7 +3,7 @@ import { multiWordObjectFilter, pluralizeFromNumber } from '@jetstream/shared/ut import { MapOf, QueryFieldWithPolymorphic, SalesforceOrgUi } from '@jetstream/types'; import { Accordion, Badge, DesertIllustration, EmptyState, Grid, GridCol, SearchInput } from '@jetstream/ui'; import type { ChildRelationship } from 'jsforce'; -import React, { Fragment, FunctionComponent, useState } from 'react'; +import { Fragment, FunctionComponent, useState } from 'react'; import { useRecoilValue } from 'recoil'; import * as fromQueryState from '../query.state'; import QueryChildFields from './QueryChildFields'; diff --git a/apps/jetstream/src/app/components/query/QueryHistory/QueryHistory.tsx b/apps/jetstream/src/app/components/query/QueryHistory/QueryHistory.tsx index f1bcc8c6a..7b0bba695 100644 --- a/apps/jetstream/src/app/components/query/QueryHistory/QueryHistory.tsx +++ b/apps/jetstream/src/app/components/query/QueryHistory/QueryHistory.tsx @@ -1,23 +1,44 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import { css } from '@emotion/react'; import { logger } from '@jetstream/shared/client-logger'; import { ANALYTICS_KEYS } from '@jetstream/shared/constants'; -import { formatNumber, hasModifierKey, isHKey, useGlobalEventHandler, useNonInitialEffect } from '@jetstream/shared/ui-utils'; +import { + formatNumber, + hasModifierKey, + hasShiftModifierKey, + isHKey, + useGlobalEventHandler, + useNonInitialEffect, +} from '@jetstream/shared/ui-utils'; import { multiWordObjectFilter } from '@jetstream/shared/utils'; import { QueryHistoryItem, QueryHistorySelection, SalesforceOrgUi, UpDown } from '@jetstream/types'; -import { EmptyState, Grid, GridCol, Icon, List, Modal, SearchInput, Spinner } from '@jetstream/ui'; +import { + ButtonGroupContainer, + EmptyState, + Grid, + GridCol, + Icon, + KeyboardShortcut, + List, + Modal, + SearchInput, + Spinner, + Tooltip, + getModifierKey, +} from '@jetstream/ui'; import classNames from 'classnames'; import { createRef, forwardRef, useCallback, useEffect, useImperativeHandle, useState } from 'react'; import { ErrorBoundary } from 'react-error-boundary'; import { useLocation } from 'react-router-dom'; import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil'; -import { useAmplitude } from '../../core/analytics'; import ErrorBoundaryFallback from '../../core/ErrorBoundaryFallback'; -import * as fromQueryHistoryState from './query-history.state'; +import { useAmplitude } from '../../core/analytics'; import QueryHistoryEmptyState from './QueryHistoryEmptyState'; import QueryHistoryItemCard from './QueryHistoryItemCard'; import QueryHistoryWhichOrg from './QueryHistoryWhichOrg'; import QueryHistoryWhichType from './QueryHistoryWhichType'; +import * as fromQueryHistoryState from './query-history.state'; const SHOWING_STEP = 25; @@ -60,9 +81,7 @@ export const QueryHistory = forwardRef(({ className, sel useImperativeHandle(ref, () => { return { open: (type: fromQueryHistoryState.QueryHistoryType = 'HISTORY') => { - setIsOpen(true); - setWhichType(type); - setWhichOrg('ALL'); + handleOpenModal(type, 'externalAction'); }, }; }); @@ -72,9 +91,10 @@ export const QueryHistory = forwardRef(({ className, sel if (!isOpen && hasModifierKey(event as any) && isHKey(event as any)) { event.stopPropagation(); event.preventDefault(); - setIsOpen(true); + handleOpenModal(hasShiftModifierKey(event as any) ? 'SAVED' : 'HISTORY', 'keyboardShortcut'); } }, + // eslint-disable-next-line react-hooks/exhaustive-deps [isOpen] ); @@ -131,12 +151,6 @@ export const QueryHistory = forwardRef(({ className, sel } }, [filteredQueryHistory, showingUpTo]); - useEffect(() => { - if (isOpen) { - trackEvent(ANALYTICS_KEYS.query_HistoryModalOpened); - } - }, [isOpen, trackEvent]); - useEffect(() => { if (isOpen) { setIsOpen(false); @@ -162,9 +176,11 @@ export const QueryHistory = forwardRef(({ className, sel // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectObjectsList, filterValue]); - function handleOpenModal() { + function handleOpenModal(type: fromQueryHistoryState.QueryHistoryType = 'HISTORY', source = 'buttonClick') { + setWhichType(type); setIsOpen(true); fromQueryHistoryState.initQueryHistory().then((queryHistory) => setQueryHistorySateMap(queryHistory)); + trackEvent(ANALYTICS_KEYS.query_HistoryModalOpened, { source, type }); } function handleExecute({ created, lastRun, runCount, isTooling, isFavorite }: QueryHistoryItem) { @@ -229,15 +245,48 @@ export const QueryHistory = forwardRef(({ className, sel return ( - + + + View query history + +
    + } + > + + + + View saved queries + +
    + } + > + + + {isOpen && ( selectionHighlight: false, renderLineHighlight: 'none', scrollBeyondLastLine: false, + scrollbar: { + alwaysConsumeMouseWheel: false, + handleMouseWheel: false, + }, }} /> diff --git a/apps/jetstream/src/app/components/query/QueryOptions/QueryCount.tsx b/apps/jetstream/src/app/components/query/QueryOptions/QueryCount.tsx new file mode 100644 index 000000000..6fea8ed7b --- /dev/null +++ b/apps/jetstream/src/app/components/query/QueryOptions/QueryCount.tsx @@ -0,0 +1,73 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { logger } from '@jetstream/shared/client-logger'; +import { query } from '@jetstream/shared/data'; +import { formatNumber, useDebounce } from '@jetstream/shared/ui-utils'; +import { pluralizeFromNumber } from '@jetstream/shared/utils'; +import { SalesforceOrgUi } from '@jetstream/types'; +import isNumber from 'lodash/isNumber'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import { useRecoilValue } from 'recoil'; +import * as fromQueryState from '../query.state'; + +export interface QueryCountProps { + org: SalesforceOrgUi; +} + +export const QueryCount = ({ org }: QueryCountProps) => { + const isMounted = useRef(true); + const currentReq = useRef(0); + const selectedSObject = useRecoilValue(fromQueryState.selectedSObjectState); + const isTooling = useRecoilValue(fromQueryState.isTooling); + const includeDeleted = useRecoilValue(fromQueryState.queryIncludeDeletedRecordsState); + const querySoqlCount = useRecoilValue(fromQueryState.querySoqlCountState); + const debouncedQuerySoqlCount = useDebounce(querySoqlCount); + const [recordCount, setRecordCount] = useState(null); + const [loading, setLoading] = useState(false); + + const fetchRecordCount = useCallback( + async (soql: string) => { + try { + currentReq.current++; + const reqId = currentReq.current; + setLoading(true); + const results = await query(org, soql, isTooling, includeDeleted); + // in case results are out of order, ignore any stale results + if (!isMounted.current || reqId !== currentReq.current) { + return; + } + setRecordCount(results.queryResults.totalSize); + } catch (ex) { + logger.warn('Error getting record count'); + setRecordCount(null); + } finally { + setLoading(false); + } + }, + [includeDeleted, isTooling, org] + ); + + useEffect(() => { + currentReq.current++; + setRecordCount(null); + }, [org, selectedSObject]); + + useEffect(() => { + if (debouncedQuerySoqlCount) { + fetchRecordCount(debouncedQuerySoqlCount); + } else { + setRecordCount(null); + } + }, [debouncedQuerySoqlCount, fetchRecordCount]); + + if (!org || !selectedSObject || !isNumber(recordCount)) { + return null; + } + + return ( +

    + {formatNumber(recordCount)} {pluralizeFromNumber('record', recordCount)} +

    + ); +}; + +export default QueryCount; diff --git a/apps/jetstream/src/app/components/query/QueryOptions/QueryFieldFunctionRow.tsx b/apps/jetstream/src/app/components/query/QueryOptions/QueryFieldFunctionRow.tsx index 22af45b41..b7ff5cffe 100644 --- a/apps/jetstream/src/app/components/query/QueryOptions/QueryFieldFunctionRow.tsx +++ b/apps/jetstream/src/app/components/query/QueryOptions/QueryFieldFunctionRow.tsx @@ -3,7 +3,7 @@ import { REGEX } from '@jetstream/shared/utils'; import { ListItem, QueryFieldWithPolymorphic } from '@jetstream/types'; import { ComboboxWithItems, GridCol, Icon, Input, Popover, PopoverRef } from '@jetstream/ui'; import classNames from 'classnames'; -import { FieldType } from 'jsforce'; +import type { FieldType } from 'jsforce'; import { useEffect, useRef, useState } from 'react'; export interface QueryFieldFunctionRowProps { diff --git a/apps/jetstream/src/app/components/query/QueryOptions/QueryResetButton.tsx b/apps/jetstream/src/app/components/query/QueryOptions/QueryResetButton.tsx index ce66e7d9e..2b66b9895 100644 --- a/apps/jetstream/src/app/components/query/QueryOptions/QueryResetButton.tsx +++ b/apps/jetstream/src/app/components/query/QueryOptions/QueryResetButton.tsx @@ -1,12 +1,12 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ +import { ANALYTICS_KEYS } from '@jetstream/shared/constants'; import { Icon } from '@jetstream/ui'; import classNames from 'classnames'; import { FunctionComponent } from 'react'; import { useResetRecoilState } from 'recoil'; -import * as fromQueryState from '../query.state'; import { useAmplitude } from '../../core/analytics'; -import { ANALYTICS_KEYS } from '@jetstream/shared/constants'; +import * as fromQueryState from '../query.state'; export interface QueryResetButtonProps { className?: string; @@ -43,7 +43,6 @@ export const QueryResetButton: FunctionComponent = ({ cla return ( - - + + + + + {/* FIXME: strongly type me! */} {!isTooling && ( - - - + executeQuery(soql, SOURCE_RELOAD, { isTooling })} + onCreateNewRecord={handleCreateNewRecord} + /> )} = React.memo(() selectedOrg={selectedOrg} sObject={sobject} soql={soql} - parsedQuery={parsedQuery} columns={queryResults?.columns?.columns || []} disabled={!hasRecords()} isTooling={isTooling} nextRecordsUrl={nextRecordsUrl} fields={fields || []} - modifiedFields={modifiedFields || []} subqueryFields={subqueryFields} records={records || []} filteredRows={filteredRows} selectedRows={selectedRows} totalRecordCount={totalRecordCount || 0} - refreshRecords={() => executeQuery(soql, SOURCE_RELOAD, { isTooling })} /> @@ -633,11 +702,13 @@ export const QueryResults: FunctionComponent = React.memo(() {!!(records && !!records.length) && ( = React.memo(() } onSelectionChanged={setSelectedRows} onFields={handleFieldsChanged} + onSubqueryFieldReorder={handleSubqueryFieldsChanged} onFilteredRowsChanged={setFilteredRows} onLoadMoreRecords={handleLoadMore} - onEdit={(record) => { - handleCloneEditView(record, 'edit'); + onSavedRecords={(data) => { + trackEvent(ANALYTICS_KEYS.query_InlineEditSave, data); + }} + onEdit={(record, source) => { + handleCloneEditView(record, 'edit', source); + }} + onClone={(record, source) => { + handleCloneEditView(record, 'clone', source); }} - onClone={(record) => { - handleCloneEditView(record, 'clone'); + onView={(record, source) => { + handleCloneEditView(record, 'view', source); }} - onView={(record) => { - handleCloneEditView(record, 'view'); + onUpdateRecords={handleUpdateRecords} + onDelete={(record) => { + handleDelete(record); }} onGetAsApex={(record) => { handleGetAsApex(record); }} + onReloadQuery={() => executeQuery(soql, RECORD_BULK_ACTION, { isTooling })} /> )} diff --git a/apps/jetstream/src/app/components/query/QueryResults/QueryResultsCopyToClipboard.tsx b/apps/jetstream/src/app/components/query/QueryResults/QueryResultsCopyToClipboard.tsx index 1f4c449f3..ded9ad982 100644 --- a/apps/jetstream/src/app/components/query/QueryResults/QueryResultsCopyToClipboard.tsx +++ b/apps/jetstream/src/app/components/query/QueryResults/QueryResultsCopyToClipboard.tsx @@ -1,10 +1,8 @@ import { ANALYTICS_KEYS } from '@jetstream/shared/constants'; -import { transformTabularDataToExcelStr, transformTabularDataToHtml } from '@jetstream/shared/ui-utils'; -import { flattenRecords } from '@jetstream/shared/utils'; +import { copyRecordsToClipboard } from '@jetstream/shared/ui-utils'; import { Maybe, Record } from '@jetstream/types'; -import { ButtonGroupContainer, DropDown, Icon, Modal, Radio, RadioGroup } from '@jetstream/ui'; +import { ButtonGroupContainer, DropDown, Icon, Modal, Radio, RadioGroup, Tooltip } from '@jetstream/ui'; import classNames from 'classnames'; -import copyToClipboard from 'copy-to-clipboard'; import { Fragment, FunctionComponent, useEffect, useState } from 'react'; import { useAmplitude } from '../../core/analytics'; @@ -31,7 +29,7 @@ export const QueryResultsCopyToClipboard: FunctionComponent { const { trackEvent } = useAmplitude(); const [isModalOpen, setIsModalOpen] = useState(false); - const [format, setFormat] = useState<'excel' | 'text' | 'json'>('excel'); + const [format, setFormat] = useState<'excel' | 'json'>('excel'); const [whichRecords, setWhichRecords] = useState('all'); const [hasFilteredRows, setHasFilteredRows] = useState(false); const [hasPartialSelectedRows, setHasPartialSelectedRows] = useState(false); @@ -52,7 +50,7 @@ export const QueryResultsCopyToClipboard: FunctionComponent - + + handleCopyToClipboard(item as 'excel' | 'text' | 'json')} + items={[{ id: 'json', value: 'Copy as JSON' }]} + onSelected={(item) => handleCopyToClipboard(item as 'excel' | 'json')} /> {isModalOpen && ( diff --git a/apps/jetstream/src/app/components/query/QueryResults/QueryResultsDownloadButton.tsx b/apps/jetstream/src/app/components/query/QueryResults/QueryResultsDownloadButton.tsx index 1aec3ef98..6ecdea7c5 100644 --- a/apps/jetstream/src/app/components/query/QueryResults/QueryResultsDownloadButton.tsx +++ b/apps/jetstream/src/app/components/query/QueryResults/QueryResultsDownloadButton.tsx @@ -1,56 +1,51 @@ import { QueryResultsColumn } from '@jetstream/api-interfaces'; +import { logger } from '@jetstream/shared/client-logger'; import { ANALYTICS_KEYS } from '@jetstream/shared/constants'; import { AsyncJobNew, BulkDownloadJob, FileExtCsvXLSXJsonGSheet, MapOf, Maybe, SalesforceOrgUi } from '@jetstream/types'; -import { ButtonGroupContainer, DownloadFromServerOpts, DropDown, Icon, RecordDownloadModal } from '@jetstream/ui'; +import { DownloadFromServerOpts, Icon, RecordDownloadModal } from '@jetstream/ui'; import { Fragment, FunctionComponent, useState } from 'react'; -import { useRecoilState } from 'recoil'; -import { Query } from 'soql-parser-js'; +import { useRecoilState, useRecoilValue } from 'recoil'; +import { composeQuery, parseQuery } from 'soql-parser-js'; import { applicationCookieState } from '../../../app-state'; import { useAmplitude } from '../../core/analytics'; import * as fromJetstreamEvents from '../../core/jetstream-events'; -import BulkUpdateFromQueryModal from './BulkUpdateFromQuery/BulkUpdateFromQueryModal'; +import * as fromQueryState from '../query.state'; export interface QueryResultsDownloadButtonProps { selectedOrg: SalesforceOrgUi; sObject?: Maybe; soql: string; - parsedQuery: Maybe; columns?: QueryResultsColumn[]; disabled: boolean; isTooling: boolean; nextRecordsUrl: Maybe; fields: string[]; - modifiedFields: string[]; subqueryFields: Maybe>; records: any[]; filteredRows: any[]; selectedRows: any[]; totalRecordCount: number; - refreshRecords: () => void; } export const QueryResultsDownloadButton: FunctionComponent = ({ selectedOrg, sObject, soql, - parsedQuery, columns, disabled, isTooling, nextRecordsUrl, fields, - modifiedFields, subqueryFields, records, filteredRows, selectedRows, totalRecordCount, - refreshRecords, }) => { const { trackEvent } = useAmplitude(); const [{ google_apiKey, google_appId, google_clientId, serverUrl }] = useRecoilState(applicationCookieState); const [isDownloadModalOpen, setModalOpen] = useState(false); - const [isBulkUpdateModalOpen, setIsBulkUpdateModalOpen] = useState(false); + const includeDeletedRecords = useRecoilValue(fromQueryState.queryIncludeDeletedRecordsState); function handleDidDownload(fileFormat: FileExtCsvXLSXJsonGSheet, whichFields: 'all' | 'specified', includeSubquery: boolean) { trackEvent(ANALYTICS_KEYS.query_DownloadResults, { @@ -64,8 +59,41 @@ export const QueryResultsDownloadButton: FunctionComponent { + if (field.type === 'Field' || field.type === 'FieldTypeof') { + return fieldValues.has(field.field); + } + if (field.type === 'FieldRelationship' && field.rawValue) { + return fieldValues.has(field.rawValue); + } + if (field.type === 'FieldSubquery' && !includeSubquery) { + return false; + } + return true; + }); + _soql = composeQuery(query); + } catch (ex) { + logger.warn('Failed processing or parse SOQL query', ex); + } + const jobs: AsyncJobNew[] = [ { type: 'BulkDownload', @@ -74,8 +102,9 @@ export const QueryResultsDownloadButton: FunctionComponent - - - handleAction(item as 'bulk-update')} - /> - + {isDownloadModalOpen && ( setModalOpen(false)} onDownload={handleDidDownload} + includeDeletedRecords={includeDeletedRecords} onDownloadFromServer={handleDownloadFromServer} /> )} - {isBulkUpdateModalOpen && sObject && totalRecordCount && parsedQuery && ( - - )} ); }; diff --git a/apps/jetstream/src/app/components/query/QueryResults/QueryResultsGetRecAsApexFieldOptions.tsx b/apps/jetstream/src/app/components/query/QueryResults/QueryResultsGetRecAsApexFieldOptions.tsx index a94efc305..35646c7b8 100644 --- a/apps/jetstream/src/app/components/query/QueryResults/QueryResultsGetRecAsApexFieldOptions.tsx +++ b/apps/jetstream/src/app/components/query/QueryResults/QueryResultsGetRecAsApexFieldOptions.tsx @@ -2,11 +2,12 @@ import { useNonInitialEffect } from '@jetstream/shared/ui-utils'; import { Checkbox, Grid } from '@jetstream/ui'; import type { Field } from 'jsforce'; import { FunctionComponent, useEffect, useState } from 'react'; +import { RecordToApexOptionsInitialOptions } from '../utils/query-apex-utils'; export interface QueryResultsGetRecAsApexFieldOptionsProps { - record: any; fieldMetadata: Field[]; onFields: (fields: string[]) => void; + onOptionsChange: (partialOptions: Partial) => void; } const SYS_FIELDS = new Set([ @@ -22,9 +23,9 @@ const SYS_FIELDS = new Set([ ]); export const QueryResultsGetRecAsApexFieldOptions: FunctionComponent = ({ - record, fieldMetadata, onFields, + onOptionsChange, }) => { const [nulls, setNulls] = useState(false); const [readOnly, setReadOnly] = useState(false); @@ -37,13 +38,6 @@ export const QueryResultsGetRecAsApexFieldOptions: FunctionComponent { - const value = record[field.name]; - if (value === undefined) { - return false; - } - if (!nulls && value === null) { - return false; - } if (!readOnly && !field.createable) { return false; } @@ -53,9 +47,6 @@ export const QueryResultsGetRecAsApexFieldOptions: FunctionComponent field.name) @@ -67,6 +58,13 @@ export const QueryResultsGetRecAsApexFieldOptions: FunctionComponent { + onOptionsChange({ + includeNullFields: nulls, + includeBooleanIfFalse: booleanIfFalse, + }); + }, [booleanIfFalse, nulls, onOptionsChange]); + return (
    diff --git a/apps/jetstream/src/app/components/query/QueryResults/QueryResultsGetRecAsApexGenerateOptions.tsx b/apps/jetstream/src/app/components/query/QueryResults/QueryResultsGetRecAsApexGenerateOptions.tsx index 3ba674918..5d17684da 100644 --- a/apps/jetstream/src/app/components/query/QueryResults/QueryResultsGetRecAsApexGenerateOptions.tsx +++ b/apps/jetstream/src/app/components/query/QueryResults/QueryResultsGetRecAsApexGenerateOptions.tsx @@ -4,10 +4,14 @@ import { FunctionComponent, useEffect, useState } from 'react'; import { RecordToApexOptionsInitialOptions } from '../utils/query-apex-utils'; export interface QueryResultsGetRecAsApexGenerateOptionsProps { + isList: boolean; onChange: (fields: Partial) => void; } -export const QueryResultsGetRecAsApexGenerateOptions: FunctionComponent = ({ onChange }) => { +export const QueryResultsGetRecAsApexGenerateOptions: FunctionComponent = ({ + isList, + onChange, +}) => { const [options, setOptions] = useState>({ inline: true, wrapInMethod: false, @@ -31,6 +35,7 @@ export const QueryResultsGetRecAsApexGenerateOptions: FunctionComponent setOptions({ ...options, inline: value })} /> void; } export const QueryResultsGetRecAsApexModal: FunctionComponent = ({ org, - record, + records, sobjectName, onClose, }) => { @@ -35,6 +35,7 @@ export const QueryResultsGetRecAsApexModal: FunctionComponent 1; const [apex, setApex] = useState(''); const fetchFieldMetadata = useCallback(async () => { @@ -45,16 +46,8 @@ export const QueryResultsGetRecAsApexModal: FunctionComponent { - return { ...field, name: field.name.replace(replaceRegex, '') }; - }); - } const fieldTypeByApiName = metadata.data.fields.reduce((output: MapOf, field) => { - const replaceNamespace = org.orgNamespacePrefix ? `${org.orgNamespacePrefix}__` : ''; - output[field.name.replace(replaceNamespace, '')] = field.type; + output[field.name] = field.type; return output; }, {}); setLoading(false); @@ -69,7 +62,7 @@ export const QueryResultsGetRecAsApexModal: FunctionComponent { fetchFieldMetadata(); - }, [fetchFieldMetadata, org, record, sobjectName]); + }, [fetchFieldMetadata, org, records, sobjectName]); useEffect(() => { setOptions((options) => ({ ...options, fieldMetadata: fieldTypesByName })); @@ -81,13 +74,13 @@ export const QueryResultsGetRecAsApexModal: FunctionComponent { if (!loading && !hasError) { - setApex(recordToApex(record, options)); + setApex(hasMultipleRecords ? recordsToApex(records, options) : recordToApex(records[0], options)); } - }, [hasError, loading, options, record]); + }, [hasError, hasMultipleRecords, loading, options, records]); - function handleOptionsChange(partialOptions: Partial) { + const handleOptionsChange = useCallback((partialOptions: Partial) => { setOptions((options) => ({ ...options, ...partialOptions })); - } + }, []); function handleCopyToClipboard() { copyToClipboard(apex, { format: 'text/plain' }); @@ -129,11 +122,11 @@ export const QueryResultsGetRecAsApexModal: FunctionComponent setFields(fields)} + onOptionsChange={handleOptionsChange} /> {/* Generation Options */} - +
    + } > - - Submit - + + } > diff --git a/apps/jetstream/src/app/components/salesforce-api/SalesforceApiResponse.tsx b/apps/jetstream/src/app/components/salesforce-api/SalesforceApiResponse.tsx index e19170f5b..b664c4b7d 100644 --- a/apps/jetstream/src/app/components/salesforce-api/SalesforceApiResponse.tsx +++ b/apps/jetstream/src/app/components/salesforce-api/SalesforceApiResponse.tsx @@ -13,7 +13,11 @@ export interface SalesforceApiResponseProps { export const SalesforceApiResponse: FunctionComponent = ({ loading, request, results }) => { return ( - }> + } + > {loading && } { } return ( - -
    - -
    - {isEnabled && ( -
    - + <> + +
    +
    - )} -
    + {isEnabled && ( +
    + +
    + )} + +

    This controls logs tracked in your browser, enabled if requested by Jetstream Support.

    + ); }; diff --git a/apps/jetstream/src/app/components/settings/Settings.tsx b/apps/jetstream/src/app/components/settings/Settings.tsx index a844256f5..93fdabc83 100644 --- a/apps/jetstream/src/app/components/settings/Settings.tsx +++ b/apps/jetstream/src/app/components/settings/Settings.tsx @@ -1,12 +1,36 @@ import { logger } from '@jetstream/shared/client-logger'; import { ANALYTICS_KEYS, TITLES } from '@jetstream/shared/constants'; -import { deleteUserProfile, getFullUserProfile, resendVerificationEmail, updateUserProfile } from '@jetstream/shared/data'; +import { + deleteUserProfile, + getFullUserProfile, + getUserProfile as getUserProfileUi, + resendVerificationEmail, + updateUserProfile, +} from '@jetstream/shared/data'; import { eraseCookies, useRollbar, useTitle } from '@jetstream/shared/ui-utils'; -import { Auth0ConnectionName, Maybe, UserProfileAuth0Identity, UserProfileAuth0Ui, UserProfileUi } from '@jetstream/types'; -import { AutoFullHeightContainer, Page, PageHeader, PageHeaderRow, PageHeaderTitle, ScopedNotification, Spinner } from '@jetstream/ui'; +import { + Auth0ConnectionName, + Maybe, + UserProfileAuth0Identity, + UserProfileAuth0Ui, + UserProfileUi, + UserProfileUiWithIdentities, +} from '@jetstream/types'; +import { + AutoFullHeightContainer, + CheckboxToggle, + Page, + PageHeader, + PageHeaderRow, + PageHeaderTitle, + ScopedNotification, + Spinner, + fireToast, +} from '@jetstream/ui'; import localforage from 'localforage'; import { Fragment, FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'; -import { fireToast } from '../core/AppToast'; +import { useSetRecoilState } from 'recoil'; +import { userProfileState } from '../../app-state'; import { useAmplitude } from '../core/analytics'; import LoggerConfig from './LoggerConfig'; import SettingsDeleteAccount from './SettingsDeleteAccount'; @@ -28,8 +52,9 @@ export const Settings: FunctionComponent = ({ userProfile, featur const rollbar = useRollbar(); const [loading, setLoading] = useState(false); const [loadingError, setLoadingError] = useState(false); - const [fullUserProfile, setFullUserProfile] = useState(); - const [modifiedUser, setModifiedUser] = useState>(); + const setUserProfile = useSetRecoilState(userProfileState); + const [fullUserProfile, setFullUserProfile] = useState(); + const [modifiedUser, setModifiedUser] = useState(); const [editMode, setEditMode] = useState(false); const { linkAccount, unlinkAccount, loading: linkAccountLoading } = useLinkAccount(); @@ -61,17 +86,19 @@ export const Settings: FunctionComponent = ({ userProfile, featur useEffect(() => { if (fullUserProfile) { - setModifiedUser({ name: fullUserProfile.name }); + setModifiedUser({ ...fullUserProfile }); } }, [fullUserProfile]); - async function handleSave() { + async function handleSave(_modifiedUser?: UserProfileUiWithIdentities) { try { - if (!modifiedUser) { + _modifiedUser = _modifiedUser || modifiedUser; + if (!_modifiedUser) { return; } setLoading(true); - const userProfile = await updateUserProfile(modifiedUser); + const userProfile = await updateUserProfile(_modifiedUser); + setUserProfile(await getUserProfileUi()); setFullUserProfile(userProfile); trackEvent(ANALYTICS_KEYS.settings_update_user); } catch (ex) { @@ -89,7 +116,7 @@ export const Settings: FunctionComponent = ({ userProfile, featur function handleCancelEdit() { setEditMode(false); - fullUserProfile && setModifiedUser({ name: fullUserProfile.name }); + fullUserProfile && setModifiedUser({ ...fullUserProfile }); } async function handleUnlinkAccount(identity: UserProfileAuth0Identity) { @@ -124,7 +151,13 @@ export const Settings: FunctionComponent = ({ userProfile, featur } function handleProfileChange(modified: Pick) { - setModifiedUser(modified); + setModifiedUser((priorValue) => ({ ...fullUserProfile, ...priorValue, ...modified } as UserProfileUiWithIdentities)); + } + + function handleFrontdoorLoginChange(skipFrontdoorLogin: boolean) { + const _modifiedUser = { ...modifiedUser, preferences: { skipFrontdoorLogin } } as UserProfileUiWithIdentities; + setModifiedUser(_modifiedUser); + handleSave(_modifiedUser); } function handleLinkAccount(connection: Auth0ConnectionName) { @@ -184,6 +217,15 @@ export const Settings: FunctionComponent = ({ userProfile, featur onSave={handleSave} onCancel={handleCancelEdit} /> + + + void; onResendVerificationEmail: (identity: UserProfileAuth0Identity) => void; @@ -62,7 +62,7 @@ export const SettingsIdentityCard: FunctionComponent const name = profileData?.name || fallback.name; const email = profileData?.email || fallback.email; const picture = profileData?.picture || fallback.picture; - const emailVerified = profileData?.email_verified || fallback.email_verified; + const emailVerified = profileData?.email_verified || fallback.emailVerified; async function confirmUnlink() { if ( diff --git a/apps/jetstream/src/app/components/settings/SettingsLinkedAccounts.tsx b/apps/jetstream/src/app/components/settings/SettingsLinkedAccounts.tsx index 963416bf0..66d12e7ff 100644 --- a/apps/jetstream/src/app/components/settings/SettingsLinkedAccounts.tsx +++ b/apps/jetstream/src/app/components/settings/SettingsLinkedAccounts.tsx @@ -1,10 +1,10 @@ -import { Auth0ConnectionName, UserProfileAuth0Identity, UserProfileAuth0Ui } from '@jetstream/types'; +import { Auth0ConnectionName, UserProfileAuth0Identity, UserProfileUiWithIdentities } from '@jetstream/types'; import { Grid } from '@jetstream/ui'; import { FunctionComponent } from 'react'; import SettingsIdentityCard from './SettingsIdentityCard'; export interface SettingsLinkedAccountsProps { - fullUserProfile: UserProfileAuth0Ui; + fullUserProfile: UserProfileUiWithIdentities; onLink: (connection: Auth0ConnectionName) => void; onUnlink: (identity: UserProfileAuth0Identity) => void; onResendVerificationEmail: (identity: UserProfileAuth0Identity) => void; diff --git a/apps/jetstream/src/app/components/settings/SettingsUserProfile.tsx b/apps/jetstream/src/app/components/settings/SettingsUserProfile.tsx index 27847101b..b5b65d223 100644 --- a/apps/jetstream/src/app/components/settings/SettingsUserProfile.tsx +++ b/apps/jetstream/src/app/components/settings/SettingsUserProfile.tsx @@ -1,9 +1,10 @@ -import { UserProfileAuth0Ui } from '@jetstream/types'; +import { css } from '@emotion/react'; +import { UserProfileUiWithIdentities } from '@jetstream/types'; import { Form, FormRow, FormRowItem, Grid, Input, ReadOnlyFormItem } from '@jetstream/ui'; -import { Fragment, FunctionComponent } from 'react'; +import { Fragment, FunctionComponent, useMemo } from 'react'; export interface SettingsUserProfileProps { - fullUserProfile: UserProfileAuth0Ui; + fullUserProfile: UserProfileUiWithIdentities; name: string; editMode: boolean; onEditMode: (value: true) => void; @@ -23,6 +24,11 @@ export const SettingsUserProfile: FunctionComponent = }) => { const invalidName = !name || name.length > 255; + const blockNameEdit = useMemo( + () => fullUserProfile.identities.some((identity) => identity.provider !== 'auth0'), + [fullUserProfile.identities] + ); + return ( @@ -34,12 +40,17 @@ export const SettingsUserProfile: FunctionComponent =
    */}
    - +
    {!editMode && ( - onEditMode(true)}> + onEditMode(true)}> {fullUserProfile.name} )} @@ -77,7 +88,7 @@ export const SettingsUserProfile: FunctionComponent = )}
    - +
    ); }; diff --git a/apps/jetstream/src/app/components/shared/create-fields/create-fields-types.ts b/apps/jetstream/src/app/components/shared/create-fields/create-fields-types.ts index 8ea06414b..e9ff888d7 100644 --- a/apps/jetstream/src/app/components/shared/create-fields/create-fields-types.ts +++ b/apps/jetstream/src/app/components/shared/create-fields/create-fields-types.ts @@ -1,5 +1,16 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { ListItem, SalesforceOrgUi } from '@jetstream/types'; +export type ManualFormulaFieldType = 'string' | 'double' | 'boolean' | 'date' | 'datetime' | 'time'; + +export type ManualFormulaRecord = Record< + string, + { + value: FieldValue | null; + type: ManualFormulaFieldType; + } +>; + export type FieldDefinitionMetadata = Partial>; export type FieldDefinitions = Record; @@ -65,7 +76,7 @@ export type SalesforceFieldType = | 'LongTextArea' | 'Html'; -export type FieldDefinitionUiType = 'picklist' | 'text' | 'textarea' | 'radio' | 'checkbox'; +export type FieldDefinitionUiType = 'picklist' | 'text' | 'textarea' | 'textarea-with-formula' | 'radio' | 'checkbox'; export interface FieldValueState { value: FieldValue; diff --git a/apps/jetstream/src/app/components/shared/create-fields/create-fields-utils.tsx b/apps/jetstream/src/app/components/shared/create-fields/create-fields-utils.tsx index 878d330e8..779daed47 100644 --- a/apps/jetstream/src/app/components/shared/create-fields/create-fields-utils.tsx +++ b/apps/jetstream/src/app/components/shared/create-fields/create-fields-utils.tsx @@ -1,7 +1,8 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { logger } from '@jetstream/shared/client-logger'; import { describeGlobal, genericRequest, queryAllFromList, queryWithCache } from '@jetstream/shared/data'; import { REGEX, ensureBoolean, splitArrayToMaxSize } from '@jetstream/shared/utils'; -import { CompositeResponse, GlobalValueSetRequest, MapOf, SalesforceOrgUi, ToolingApiResponse } from '@jetstream/types'; +import { CompositeResponse, GlobalValueSetRequest, MapOf, Maybe, SalesforceOrgUi, ToolingApiResponse } from '@jetstream/types'; import type { DescribeGlobalSObjectResult } from 'jsforce'; import isBoolean from 'lodash/isBoolean'; import isNil from 'lodash/isNil'; @@ -119,7 +120,7 @@ export const fieldDefinitions: FieldDefinitions = { values: async (org, skipRequestCache) => { return (await describeGlobal(org, false, skipRequestCache)).data.sobjects .filter((obj) => !(obj as any).associateEntityType && obj.triggerable && obj.queryable) - .map(({ name, label }) => ({ id: name, value: name, label: label, secondaryLabel: name })); + .map(({ name, label }) => ({ id: name, value: name, label: label, secondaryLabel: name, secondaryLabelOnNewLine: true })); }, required: true, }, @@ -171,12 +172,13 @@ export const fieldDefinitions: FieldDefinitions = { precision: { label: 'Length', type: 'text', // number + labelHelp: 'Number of digits to the left of the decimal point', validate: (value: string) => { if (!value || !/^[0-9]+$/.test(value)) { return false; } const numValue = Number(value); - return isFinite(numValue) && numValue >= 0 && numValue <= 18; + return isFinite(numValue) && numValue >= 1 && numValue <= 18; }, invalidErrorMessage: 'Must be between 0 and 18', required: true, @@ -184,14 +186,15 @@ export const fieldDefinitions: FieldDefinitions = { scale: { label: 'Decimal Places', type: 'text', + labelHelp: 'Number of digits to the right of the decimal point', validate: (value: string) => { if (!value || !/^[0-9]+$/.test(value)) { return false; } const numValue = Number(value); - return isFinite(numValue) && numValue >= 0 && numValue <= 18; + return isFinite(numValue) && numValue >= 0 && numValue <= 17; }, - invalidErrorMessage: 'Must be between 0 and 18', + invalidErrorMessage: 'Must be between 0 and 17', required: true, }, required: { @@ -301,9 +304,8 @@ export const fieldDefinitions: FieldDefinitions = { }, formula: { label: 'Formula', - type: 'textarea', + type: 'textarea-with-formula', required: true, - // TODO: would be cool to have syntax highlighting }, formulaTreatBlanksAs: { label: 'Treat Blanks As', @@ -800,11 +802,11 @@ export function isFieldValues(input: FieldValues | FieldDefinitionMetadata): inp return !isNil((input as any)?._key); } -export function preparePayload(sobjects: string[], rows: FieldValues[]): FieldDefinitionMetadata[] { - return rows.flatMap((row) => sobjects.map((sobject) => prepareFieldPayload(sobject, row))); +export function preparePayload(sobjects: string[], rows: FieldValues[], orgNamespace?: Maybe): FieldDefinitionMetadata[] { + return rows.flatMap((row) => sobjects.map((sobject) => prepareFieldPayload(sobject, row, orgNamespace))); } -function prepareFieldPayload(sobject: string, fieldValues: FieldValues): FieldDefinitionMetadata { +function prepareFieldPayload(sobject: string, fieldValues: FieldValues, orgNamespace?: Maybe): FieldDefinitionMetadata { const fieldMetadata: FieldDefinitionMetadata = [ ...baseFields, ...fieldTypeDependencies[fieldValues.type.value as FieldDefinitionType], @@ -816,7 +818,17 @@ function prepareFieldPayload(sobject: string, fieldValues: FieldValues): FieldDe return output; }, {}); // prefix with object - fieldMetadata.fullName = `${sobject}.${fieldMetadata.fullName}__c`; + const fieldApiName = orgNamespace ? `${orgNamespace}__${fieldMetadata.fullName}__c` : `${fieldMetadata.fullName}__c`; + fieldMetadata.fullName = `${sobject}.${fieldApiName}`; + + if (fieldMetadata.scale && fieldMetadata.precision) { + const scale = Number(fieldMetadata.scale); + const precision = Number(fieldMetadata.precision); + if (!Number.isNaN(scale) && !Number.isNaN(precision)) { + fieldMetadata.scale = scale; + fieldMetadata.precision = scale + precision; + } + } if (fieldValues.type.value === 'Formula') { fieldMetadata.type = fieldValues.secondaryType.value; diff --git a/apps/jetstream/src/app/components/shared/create-fields/useCreateFields.ts b/apps/jetstream/src/app/components/shared/create-fields/useCreateFields.ts index 011f57f7f..25af21c22 100644 --- a/apps/jetstream/src/app/components/shared/create-fields/useCreateFields.ts +++ b/apps/jetstream/src/app/components/shared/create-fields/useCreateFields.ts @@ -101,7 +101,7 @@ export default function useCreateFields({ (rows: FieldValues[]) => { if (rows?.length && sObjects?.length) { try { - const payload: CreateFieldsResults[] = preparePayload(sObjects, rows).map( + const payload: CreateFieldsResults[] = preparePayload(sObjects, rows, selectedOrg.orgNamespacePrefix).map( (field): CreateFieldsResults => ({ key: `${(field.fullName as string).replace('.', '_').replace(REGEX.CONSECUTIVE_UNDERSCORES, '_')}`, label: field.fullName, @@ -127,7 +127,7 @@ export default function useCreateFields({ } return false; }, - [rollbar, sObjects] + [rollbar, sObjects, selectedOrg.orgNamespacePrefix] ); /** diff --git a/apps/jetstream/src/app/components/formula-evaluator/FormulaEvaluatorRecordSearch.tsx b/apps/jetstream/src/app/components/shared/formula-evaluator/FormulaEvaluatorRecordSearch.tsx similarity index 75% rename from apps/jetstream/src/app/components/formula-evaluator/FormulaEvaluatorRecordSearch.tsx rename to apps/jetstream/src/app/components/shared/formula-evaluator/FormulaEvaluatorRecordSearch.tsx index ff88ab8bb..d0cdade64 100644 --- a/apps/jetstream/src/app/components/formula-evaluator/FormulaEvaluatorRecordSearch.tsx +++ b/apps/jetstream/src/app/components/shared/formula-evaluator/FormulaEvaluatorRecordSearch.tsx @@ -1,11 +1,12 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { logger } from '@jetstream/shared/client-logger'; -import { query } from '@jetstream/shared/data'; +import { describeSObject, query } from '@jetstream/shared/data'; import { CloneEditView, ListItem, Maybe, SalesforceOrgUi } from '@jetstream/types'; import { ComboboxWithItemsTypeAhead, Grid, Icon, Tooltip } from '@jetstream/ui'; -import { FunctionComponent, useCallback, useState } from 'react'; +import { FunctionComponent, useCallback, useRef, useState } from 'react'; import { useRecoilState } from 'recoil'; -import { applicationCookieState } from '../../app-state'; -import ViewEditCloneRecord from '../core/ViewEditCloneRecord'; +import { applicationCookieState } from '../../../app-state'; +import ViewEditCloneRecord from '../../core/ViewEditCloneRecord'; export interface FormulaEvaluatorRecordSearchProps { selectedOrg: SalesforceOrgUi; @@ -22,6 +23,7 @@ export const FormulaEvaluatorRecordSearch: FunctionComponent { + const nameField = useRef<{ selectedSObject: string; nameField: string }>(); const [{ defaultApiVersion }] = useRecoilState(applicationCookieState); const [records, setRecords] = useState[]>([]); const [selectedRecord, setSelectedRecords] = useState | null>(null); @@ -37,19 +39,29 @@ export const FormulaEvaluatorRecordSearch: FunctionComponent result.data.fields.find((field) => field.nameField)?.name || 'Name' + ), + }; + } + const name = nameField.current.nameField; + + let soql = `SELECT Id, ${name} FROM ${selectedSObject} ORDER BY ${name} LIMIT 50`; if (searchTerm) { if (searchTerm.length === 15 || searchTerm.length === 18) { - soql = `SELECT Id, Name FROM ${selectedSObject} WHERE Id = '${searchTerm}' OR Name LIKE '%${searchTerm}%' ORDER BY Name LIMIT 50`; + soql = `SELECT Id, ${name} FROM ${selectedSObject} WHERE Id = '${searchTerm}' OR ${name} LIKE '%${searchTerm}%' ORDER BY ${name} LIMIT 50`; } else { - soql = `SELECT Id, Name FROM ${selectedSObject} WHERE Name LIKE '%${searchTerm}%' ORDER BY Name LIMIT 50`; + soql = `SELECT Id, ${name} FROM ${selectedSObject} WHERE ${name} LIKE '%${searchTerm}%' ORDER BY ${name} LIMIT 50`; } } const { queryResults } = await query(selectedOrg, soql); setRecords( queryResults.records.map((record) => ({ id: record.Id, - label: record.Name, + label: record[name], title: record.Id, secondaryLabel: record.Id, secondaryLabelOnNewLine: true, @@ -95,7 +107,7 @@ export const FormulaEvaluatorRecordSearch: FunctionComponent; + results: { + formulaFields: formulon.FormulaData; + parsedFormula: formulon.FormulaResult; + } | null; +} + +function getValue(value: string | number | boolean | null | Date): string { + if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { + return String(value); + } + if (value instanceof Date) { + return formatISO(value, { representation: 'date' }); + } + return 'null'; +} + +export const FormulaEvaluatorResults: FunctionComponent = ({ errorMessage, results }) => { + return ( + <> + {errorMessage && ( +
    + + {errorMessage} + +
    + )} + {results && ( + + {!!Object.keys(results.formulaFields).length && ( + <> +
    Record Fields
    +
    + {Object.keys(results.formulaFields).map((field) => { + const { value } = results.formulaFields[field]; + return ( +
    + {field}: {String(value) || ''} +
    + ); + })} +
    + + )} +
    Formula Results
    +
    + {results.parsedFormula.type === 'error' ? ( + +
    {results.parsedFormula.errorType}
    +
    {results.parsedFormula.message}
    + {results.parsedFormula.errorType === 'NotImplementedError' && results.parsedFormula.name === 'isnull' && ( +
    Use ISBLANK instead
    + )} +
    + ) : ( +
    {getValue(results.parsedFormula.value) ?? ''}
    + )} +
    +
    + )} + + ); +}; + +export default FormulaEvaluatorResults; diff --git a/apps/jetstream/src/app/components/shared/formula-evaluator/FormulaEvaluatorUserSearch.tsx b/apps/jetstream/src/app/components/shared/formula-evaluator/FormulaEvaluatorUserSearch.tsx new file mode 100644 index 000000000..e3e2c5c42 --- /dev/null +++ b/apps/jetstream/src/app/components/shared/formula-evaluator/FormulaEvaluatorUserSearch.tsx @@ -0,0 +1,112 @@ +import { logger } from '@jetstream/shared/client-logger'; +import { query } from '@jetstream/shared/data'; +import { ListItem, SalesforceOrgUi } from '@jetstream/types'; +import { ComboboxWithItemsTypeAhead, Grid } from '@jetstream/ui'; +import { FunctionComponent, useCallback, useRef, useState } from 'react'; + +export interface FormulaEvaluatorUserSearchProps { + selectedOrg: SalesforceOrgUi; + disabled: boolean; + onSelectedRecord: (recordId: string) => void; +} + +export const FormulaEvaluatorUserSearch: FunctionComponent = ({ + selectedOrg, + disabled, + onSelectedRecord, +}) => { + const isFirstRun = useRef(true); + const [records, setRecords] = useState[]>([ + { + id: selectedOrg.userId, + label: `Current User - ${selectedOrg.username}`, + secondaryLabel: selectedOrg.userId, + secondaryLabelOnNewLine: true, + value: selectedOrg.userId, + }, + ]); + const [selectedRecord, setSelectedRecords] = useState | null>(records[0]); + + const handleSearch = useCallback( + async (searchTerm: string) => { + logger.log('search', searchTerm); + if (!isFirstRun.current) { + setSelectedRecords(null); + } + isFirstRun.current = false; + try { + const baseQuery = `SELECT Id, Name, Username, Profile.Name FROM User WHERE Profile.Name != null AND Id != '${selectedOrg.userId}'`; + let soql = `${baseQuery} ORDER BY Name LIMIT 100`; + if (searchTerm) { + if (searchTerm.length === 15 || searchTerm.length === 18) { + soql = `${baseQuery} AND (Id = '${searchTerm}' OR Name LIKE '%${searchTerm}%') ORDER BY Name LIMIT 100`; + } else { + soql = `${baseQuery} AND Name LIKE '%${searchTerm}%' ORDER BY Name LIMIT 100`; + } + } + const { queryResults } = await query<{ + Id: string; + Name: string; + Username: string; + Profile: { Name: string }; + }>(selectedOrg, soql); + setRecords([ + { + id: selectedOrg.userId, + label: `Current User - ${selectedOrg.username}`, + secondaryLabel: selectedOrg.userId, + secondaryLabelOnNewLine: true, + value: selectedOrg.userId, + }, + ...queryResults.records.map((record) => ({ + id: record.Id, + label: `${record.Name} - ${record.Username} (${record.Profile.Name})`, + title: record.Id, + secondaryLabel: record.Id, + secondaryLabelOnNewLine: true, + value: record.Id, + meta: record, + })), + ]); + } catch (ex) { + logger.warn('Error searching records', ex); + setRecords([ + { + id: selectedOrg.userId, + label: `Current User - ${selectedOrg.username}`, + secondaryLabel: selectedOrg.userId, + secondaryLabelOnNewLine: true, + value: selectedOrg.userId, + }, + ]); + } + }, + [selectedOrg] + ); + + function handleSelection(item: ListItem) { + setSelectedRecords(item); + onSelectedRecord(item?.value); + } + + return ( + + + + ); +}; + +export default FormulaEvaluatorUserSearch; diff --git a/apps/jetstream/src/app/components/formula-evaluator/formula-evaluator.utils.ts b/apps/jetstream/src/app/components/shared/formula-evaluator/formula-evaluator.utils.ts similarity index 64% rename from apps/jetstream/src/app/components/formula-evaluator/formula-evaluator.utils.ts rename to apps/jetstream/src/app/components/shared/formula-evaluator/formula-evaluator.utils.ts index ded9accd7..31714a7fd 100644 --- a/apps/jetstream/src/app/components/formula-evaluator/formula-evaluator.utils.ts +++ b/apps/jetstream/src/app/components/shared/formula-evaluator/formula-evaluator.utils.ts @@ -1,8 +1,8 @@ import { QueryResultsColumn, QueryResultsColumns } from '@jetstream/api-interfaces'; import { logger } from '@jetstream/shared/client-logger'; -import { query } from '@jetstream/shared/data'; +import { query, queryAll, readMetadata } from '@jetstream/shared/data'; import { getMapOf } from '@jetstream/shared/utils'; -import { MapOf, Maybe, SalesforceOrgUi } from '@jetstream/types'; +import { MapOf, Maybe, PermissionSetMetadataRecord, ProfileMetadataRecord, SalesforceOrgUi } from '@jetstream/types'; import parseISO from 'date-fns/parseISO'; import startOfDay from 'date-fns/startOfDay'; import * as formulon from 'formulon'; @@ -10,10 +10,12 @@ import { DataType, FormulaDataValue } from 'formulon'; import type { Field } from 'jsforce'; import lodashGet from 'lodash/get'; import isNil from 'lodash/isNil'; +import isString from 'lodash/isString'; import { composeQuery, getField } from 'soql-parser-js'; -import { fetchMetadataFromSoql } from '../query/utils/query-soql-utils'; -import { NullNumberBehavior } from './formula-evaluator.state'; -import { FormulaFieldsByType } from './formula-evaluator.types'; +import { NullNumberBehavior } from '../../formula-evaluator/formula-evaluator.state'; +import { FormulaFieldsByType } from '../../formula-evaluator/formula-evaluator.types'; +import { fetchMetadataFromSoql } from '../../query/utils/query-soql-utils'; +import { ManualFormulaRecord } from '../create-fields/create-fields-types'; const MATCH_FORMULA_SPECIAL_LABEL = /^\$[a-zA-Z]+\./; @@ -34,7 +36,7 @@ export function getFormulonTypeFromColumnType(col: QueryResultsColumn): DataType return 'text'; } -export function getFormulonTypeFromMetadata(col: Field): DataType { +export function getFormulonTypeFromMetadata(col: T): DataType { if (col.type === 'boolean') { return 'checkbox'; } else if (col.type === 'double' || col.type === 'currency' || col.type === 'percent' || col.type === 'int') { @@ -58,11 +60,16 @@ export function getFormulonTypeFromMetadata(col: Field): DataType { /** * Function that determines if the provided value is of type QueryResultsColumn or Field */ -function isQueryResultsColumn(col: QueryResultsColumn | Field): col is QueryResultsColumn { +function isQueryResultsColumn(col: QueryResultsColumn | Field | { type: Field['type'] }): col is QueryResultsColumn { return (col as QueryResultsColumn).booleanType !== undefined; } -export function getFormulonData(col: QueryResultsColumn | Field, value: any, numberNullBehavior = 'ZERO'): FormulaDataValue { +export function getFormulonData( + col: QueryResultsColumn | Field | { type: Field['type'] }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + value: any, + numberNullBehavior = 'ZERO' +): FormulaDataValue { const dataType = isQueryResultsColumn(col) ? getFormulonTypeFromColumnType(col) : getFormulonTypeFromMetadata(col); if (dataType === 'text') { return { @@ -75,15 +82,16 @@ export function getFormulonData(col: QueryResultsColumn | Field, value: any, num }; } if (dataType === 'number') { - const { length, scale } = isQueryResultsColumn(col) - ? { - length: getPrecision(value) - 18, - scale: getPrecision(value), - } - : { - length: (isNil(col.precision) ? getPrecision(value) : col.precision) - col.scale, - scale: col.scale, - }; + const { length, scale } = + isQueryResultsColumn(col) || !('precision' in col) || !('scale' in col) + ? { + length: getPrecision(value) - 18, + scale: getPrecision(value), + } + : { + length: (isNil(col.precision) ? getPrecision(value) : col.precision) - col.scale, + scale: col.scale, + }; return { type: 'literal', dataType, @@ -107,10 +115,7 @@ export function getFormulonData(col: QueryResultsColumn | Field, value: any, num type: 'literal', dataType, value: isNil(value) ? null : parseISO(value), - options: { - length: value.length, - scale: getPrecision(value), - }, + options: {}, }; } if (dataType === 'picklist' || dataType === 'multipicklist') { @@ -123,14 +128,6 @@ export function getFormulonData(col: QueryResultsColumn | Field, value: any, num }, }; } - // FIXME: need to test a bunch of data types here - // if (isNil(value)) { - // return { - // type: 'literal', - // dataType: 'null', - // value: null, - // }; - // } return { type: 'literal', dataType, @@ -138,8 +135,11 @@ export function getFormulonData(col: QueryResultsColumn | Field, value: any, num }; } -function getPrecision(a) { - if (!isFinite(a)) return 0; +function getPrecision(a: number | string) { + a = isString(a) ? Number(a) : a; + if (!isFinite(a)) { + return 0; + } let e = 1; let p = 0; while (Math.round(a * e) / e !== a) { @@ -149,121 +149,163 @@ function getPrecision(a) { return p; } +interface FormulaDataProps { + selectedOrg: SalesforceOrgUi; + selectedUserId: string; + fields: string[]; + /** + * If recordId is provided, the record will be queried from the org + * In this case, type can be omitted, if provided it should be 'QUERY_RECORD' + */ + type?: 'QUERY_RECORD'; + recordId: string; + record?: never; + sobjectName: string; + numberNullBehavior: NullNumberBehavior; +} + +interface FormulaDataProvidedRecordProps { + selectedOrg: SalesforceOrgUi; + selectedUserId: string; + fields: string[]; + /** + * If record is provided, the record will be used directly instead of querying from the org + */ + type: 'PROVIDED_RECORD'; + recordId?: never; + record: ManualFormulaRecord; + sobjectName: string; + numberNullBehavior: NullNumberBehavior; +} + export async function getFormulaData({ selectedOrg, + selectedUserId, fields, + type = 'QUERY_RECORD', recordId, + record, sobjectName, numberNullBehavior = 'ZERO', -}: { - selectedOrg: SalesforceOrgUi; - fields: string[]; - recordId: string; - sobjectName: string; - numberNullBehavior: NullNumberBehavior; -}): Promise< +}: FormulaDataProps | FormulaDataProvidedRecordProps): Promise< | { type: 'error'; message: string } | { type: 'success'; formulaFields: formulon.FormulaData; warnings: { type: string; message: string }[] } > { - const formulaFields: formulon.FormulaData = {}; - const warnings = []; - - const { - objectFields, - apiFields, - customMetadata, - customLabels, - organization, - customPermissions, - profile, - customSettings, - system, - user, - userRole, - } = fields.reduce( - (output: FormulaFieldsByType, field) => { - if (!field.startsWith('$')) { - output.objectFields.push(field); - } else { - const identifier = field.toLowerCase().split('.')[0]; - switch (identifier) { - case '$api': - output.apiFields.push(field); - break; - case '$custommetadata': - output.customMetadata.push(field); - break; - case '$label': - output.customLabels.push(field); - break; - case '$organization': - output.organization.push(field); - break; - case '$permission': - output.customPermissions.push(field); - break; - case '$profile': - output.profile.push(field); - break; - case '$setup': - output.customSettings.push(field); - break; - case '$system': - output.system.push(field); - break; - case '$user': - output.user.push(field); - break; - case '$userrole': - output.userRole.push(field); - break; - default: - break; + try { + const formulaFields: formulon.FormulaData = {}; + const warnings = []; + + const { + objectFields, + apiFields, + customMetadata, + customLabels, + organization, + customPermissions, + profile, + customSettings, + system, + user, + userRole, + } = fields.reduce( + (output: FormulaFieldsByType, field) => { + if (!field.startsWith('$')) { + output.objectFields.push(field); + } else { + const identifier = field.toLowerCase().split('.')[0]; + switch (identifier) { + case '$api': + output.apiFields.push(field); + break; + case '$custommetadata': + output.customMetadata.push(field); + break; + case '$label': + output.customLabels.push(field); + break; + case '$organization': + output.organization.push(field); + break; + case '$permission': + output.customPermissions.push(field); + break; + case '$profile': + output.profile.push(field); + break; + case '$setup': + output.customSettings.push(field); + break; + case '$system': + output.system.push(field); + break; + case '$user': + output.user.push(field); + break; + case '$userrole': + output.userRole.push(field); + break; + default: + break; + } } + return output; + }, + { + objectFields: [], + apiFields: [], + customMetadata: [], + customLabels: [], + organization: [], + customPermissions: [], + profile: [], + customSettings: [], + system: [], + user: [], + userRole: [], } - return output; - }, - { - objectFields: [], - apiFields: [], - customMetadata: [], - customLabels: [], - organization: [], - customPermissions: [], - profile: [], - customSettings: [], - system: [], - user: [], - userRole: [], + ); + + // TODO: this is a good candidate for unit tests + // TODO: collect warnings + // These should also be somewhat forgiving + if (type === 'QUERY_RECORD' && recordId) { + await collectBaseQueriedRecordFields({ selectedOrg, fields: objectFields, recordId, sobjectName, formulaFields, numberNullBehavior }); + } else { + await collectBaseRecordFields({ + fields, + record: record || {}, + formulaFields, + numberNullBehavior, + }); } - ); - // TODO: this is a good candidate for unit tests - // TODO: collect warnings - // These should also be somewhat forgiving - await collectBaseRecordFields({ selectedOrg, fields: objectFields, recordId, sobjectName, formulaFields, numberNullBehavior }); - collectApiFields({ selectedOrg, fields: apiFields, formulaFields, numberNullBehavior }); - await collectCustomMetadata({ selectedOrg, fields: customMetadata, formulaFields, numberNullBehavior }); - await collectCustomSettingFields({ selectedOrg, fields: customSettings, formulaFields, numberNullBehavior }); - await collectCustomPermissions({ selectedOrg, fields: customPermissions, formulaFields, numberNullBehavior }); - await collectLabels({ selectedOrg, fields: customLabels, formulaFields, numberNullBehavior }); - await collectOrganizationFields({ selectedOrg, fields: organization, formulaFields, numberNullBehavior }); - await collectUserProfileAndRoleFields({ - selectedOrg, - userFields: user, - profileFields: profile, - roleFields: userRole, - formulaFields, - numberNullBehavior, - }); - await collectSystemFields({ fields: system, formulaFields }); + collectApiFields({ selectedOrg, fields: apiFields, formulaFields, numberNullBehavior }); + await collectCustomMetadata({ selectedOrg, fields: customMetadata, formulaFields, numberNullBehavior }); + await collectCustomSettingFields({ selectedOrg, selectedUserId, fields: customSettings, formulaFields, numberNullBehavior }); + await collectCustomPermissions({ selectedOrg, selectedUserId, fields: customPermissions, formulaFields, numberNullBehavior }); + await collectLabels({ selectedOrg, fields: customLabels, formulaFields, numberNullBehavior }); + await collectOrganizationFields({ selectedOrg, fields: organization, formulaFields, numberNullBehavior }); + await collectUserProfileAndRoleFields({ + selectedOrg, + selectedUserId, + userFields: user, + profileFields: profile, + roleFields: userRole, + formulaFields, + numberNullBehavior, + }); + await collectSystemFields({ fields: system, formulaFields }); - logger.log({ formulaFields, warnings }); + logger.log({ formulaFields, warnings }); - return { type: 'success', formulaFields, warnings }; + return { type: 'success', formulaFields, warnings }; + } catch (ex) { + logger.error(ex); + throw ex; + } } -async function collectBaseRecordFields({ +async function collectBaseQueriedRecordFields({ selectedOrg, fields, recordId, @@ -281,6 +323,7 @@ async function collectBaseRecordFields({ if (!fields.length) { return; } + const { queryResults, columns, parsedQuery } = await query( selectedOrg, composeQuery({ @@ -322,6 +365,33 @@ async function collectBaseRecordFields({ }); } +async function collectBaseRecordFields({ + fields, + record, + formulaFields, + numberNullBehavior, +}: { + fields: string[]; + record: ManualFormulaRecord; + formulaFields: formulon.FormulaData; + numberNullBehavior: NullNumberBehavior; +}) { + if (!fields.length) { + return; + } + + fields.forEach((field) => { + // Prefer to get field from actual metadata, otherwise picklist is not handled and can cause formula errors + const recordValue = record[field]; + if (!recordValue) { + throw new Error(`Field ${field} does not exist in provided record data.`); + } + const { type, value } = recordValue; + + formulaFields[field] = getFormulonData({ type }, value, numberNullBehavior); + }); +} + function collectApiFields({ selectedOrg, fields, @@ -472,6 +542,7 @@ async function collectLabels({ fields.forEach((fieldWithIdentifier) => { const field = fieldWithIdentifier.replace(MATCH_FORMULA_SPECIAL_LABEL, ''); const recordName = field.toLowerCase(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const record: Record | undefined = recordsByApiName[recordName]; formulaFields[fieldWithIdentifier] = { type: 'literal', @@ -521,6 +592,7 @@ async function collectOrganizationFields({ async function collectUserProfileAndRoleFields({ selectedOrg, + selectedUserId, userFields, profileFields, roleFields, @@ -528,6 +600,7 @@ async function collectUserProfileAndRoleFields({ numberNullBehavior, }: { selectedOrg: SalesforceOrgUi; + selectedUserId: string; userFields: string[]; profileFields: string[]; roleFields: string[]; @@ -554,7 +627,7 @@ async function collectUserProfileAndRoleFields({ where: { left: { field: 'Id', - value: selectedOrg.userId, + value: selectedUserId, operator: '=', literalType: 'STRING', }, @@ -589,13 +662,21 @@ async function collectUserProfileAndRoleFields({ }); } +/** + * Fetch all assigned permission sets (which also include the user's profile) + * read metadata for each assigned permission set (slow) + * Get all the customPermissions assigned to the profile/permission sets + * compare with the formula's custom permission + */ async function collectCustomPermissions({ selectedOrg, + selectedUserId, fields, formulaFields, numberNullBehavior, }: { selectedOrg: SalesforceOrgUi; + selectedUserId: string; fields: string[]; formulaFields: formulon.FormulaData; numberNullBehavior: NullNumberBehavior; @@ -604,16 +685,88 @@ async function collectCustomPermissions({ return; } - // TODO: not sure how to tackle these yet + // Get all assigned permission sets and assigned profile + const { queryResults } = await queryAll<{ + Id: string; + PermissionSetId: string; + PermissionSet: + | { Name: string; IsOwnedByProfile: true; Profile: { Name: string } } + | { Name: string; IsOwnedByProfile: false; Profile: undefined }; + }>( + selectedOrg, + composeQuery({ + fields: Array.from( + new Set([ + getField('Id'), + getField('PermissionSetId'), + getField('PermissionSet.Name'), + getField('PermissionSet.IsOwnedByProfile'), + getField('PermissionSet.Profile.Name'), + ]) + ), + sObject: 'PermissionSetAssignment', + where: { + left: { field: 'AssigneeId', value: selectedUserId, operator: '=', literalType: 'STRING' }, + operator: 'AND', + right: { + left: { field: 'IsActive', value: 'TRUE', operator: '=', literalType: 'BOOLEAN' }, + operator: 'AND', + right: { + left: { field: 'IsRevoked', value: 'FALSE', operator: '=', literalType: 'BOOLEAN' }, + }, + }, + }, + }) + ); + + // Fetch metadata for assigned profile and all assigned permission sets + const permissionSetNames = queryResults.records + .filter((item) => !item.PermissionSet.IsOwnedByProfile) + .map(({ PermissionSet }) => encodeURIComponent(PermissionSet.Name)); + + const profileMetadata = await readMetadata( + selectedOrg, + 'Profile', + queryResults.records + .filter((item) => item.PermissionSet.IsOwnedByProfile) + .map(({ PermissionSet }) => encodeURIComponent(PermissionSet.Profile?.Name || '')) + ); + const permissionSets = permissionSetNames.length + ? await readMetadata(selectedOrg, 'PermissionSet', permissionSetNames) + : []; + + // Get all custom permissions assigned to profile and permission sets + const customPermissions = new Set( + [...profileMetadata, ...permissionSets].flatMap((item) => { + // readMetadata sometimes returns objects instead of array because of XML parsing done by JSForce + let customPermissions = item.customPermissions || []; + if (!Array.isArray(customPermissions)) { + customPermissions = [customPermissions]; + } + return customPermissions.filter(({ enabled }) => enabled).map(({ name }) => name); + }) + ); + + // Calculate if custom permission is enabled + fields.forEach((field) => { + const permissionName = field.split('.')[1]; + formulaFields[field] = { + type: 'literal', + dataType: 'checkbox', + value: customPermissions.has(permissionName), + }; + }); } async function collectCustomSettingFields({ selectedOrg, + selectedUserId, fields, formulaFields, numberNullBehavior, }: { selectedOrg: SalesforceOrgUi; + selectedUserId: string; fields: string[]; formulaFields: formulon.FormulaData; numberNullBehavior: NullNumberBehavior; @@ -631,7 +784,7 @@ async function collectCustomSettingFields({ where: { left: { field: 'Id', - value: selectedOrg.userId, + value: selectedUserId, operator: '=', literalType: 'STRING', }, @@ -704,8 +857,8 @@ function getFieldsByName(columns: Maybe) { ); } -function getRecordsByLowercaseField(records: Record[], field: string): Record> { - return records.reduce((output: Record>, record) => { +function getRecordsByLowercaseField(records: Record[], field: string): Record> { + return records.reduce((output: Record>, record) => { output[record[field].toLowerCase()] = record; return output; }, {}); diff --git a/apps/jetstream/src/app/components/shared/mass-update-records/MassUpdateRecordsDeploymentRow.tsx b/apps/jetstream/src/app/components/shared/mass-update-records/MassUpdateRecordsDeploymentRow.tsx index 3146de8c7..ea37fce7d 100644 --- a/apps/jetstream/src/app/components/shared/mass-update-records/MassUpdateRecordsDeploymentRow.tsx +++ b/apps/jetstream/src/app/components/shared/mass-update-records/MassUpdateRecordsDeploymentRow.tsx @@ -2,18 +2,18 @@ import { logger } from '@jetstream/shared/client-logger'; import { ANALYTICS_KEYS } from '@jetstream/shared/constants'; import { bulkApiGetRecords } from '@jetstream/shared/data'; import { formatNumber } from '@jetstream/shared/ui-utils'; -import { pluralizeFromNumber } from '@jetstream/shared/utils'; +import { decodeHtmlEntity, pluralizeFromNumber } from '@jetstream/shared/utils'; import { BulkJobBatchInfo, BulkJobResultRecord, SalesforceOrgUi } from '@jetstream/types'; import { Card, FileDownloadModal, Grid, SalesforceLogin, ScopedNotification, Spinner, SupportLink } from '@jetstream/ui'; import { Fragment, FunctionComponent, useEffect, useState } from 'react'; -import { useRecoilState } from 'recoil'; -import { applicationCookieState } from '../../../app-state'; +import { useRecoilValue } from 'recoil'; +import { applicationCookieState, selectSkipFrontdoorAuth } from '../../../app-state'; import { useAmplitude } from '../../core/analytics'; import * as fromJetstreamEvents from '../../core/jetstream-events'; import LoadRecordsResultsModal from '../../load-records/components/load-results/LoadRecordsResultsModal'; import MassUpdateRecordTransformationText from '../../update-records/shared/MassUpdateRecordTransformationText'; -import { DownloadAction, DownloadType } from '../load-records-results/load-records-results-types'; import LoadRecordsBulkApiResultsTable from '../load-records-results/LoadRecordsBulkApiResultsTable'; +import { DownloadAction, DownloadType } from '../load-records-results/load-records-results-types'; import { MetadataRow } from './mass-update-records.types'; import { getFieldsToQuery } from './mass-update-records.utils'; @@ -53,7 +53,8 @@ export const MassUpdateRecordsDeploymentRow: FunctionComponent({ open: false, data: [], header: [], fileNameParts: [] }); const [resultsModalData, setResultsModalData] = useState({ open: false, data: [], header: [], type: 'results' }); - const [{ serverUrl, google_apiKey, google_appId, google_clientId }] = useRecoilState(applicationCookieState); + const { serverUrl, google_apiKey, google_appId, google_clientId } = useRecoilValue(applicationCookieState); + const skipFrontDoorAuth = useRecoilValue(selectSkipFrontdoorAuth); const { done, processingErrors, status, jobInfo, processingEndTime, processingStartTime } = deployResults; @@ -84,7 +85,7 @@ export const MassUpdateRecordsDeploymentRow: FunctionComponent diff --git a/apps/jetstream/src/app/components/shared/mass-update-records/MassUpdateRecordsObjectRowCriteria.tsx b/apps/jetstream/src/app/components/shared/mass-update-records/MassUpdateRecordsObjectRowCriteria.tsx index d7207e0da..a5153869d 100644 --- a/apps/jetstream/src/app/components/shared/mass-update-records/MassUpdateRecordsObjectRowCriteria.tsx +++ b/apps/jetstream/src/app/components/shared/mass-update-records/MassUpdateRecordsObjectRowCriteria.tsx @@ -1,7 +1,7 @@ import { css } from '@emotion/react'; import { useDebounce } from '@jetstream/shared/ui-utils'; import { ListItem } from '@jetstream/types'; -import { ComboboxWithItems, Grid, Textarea, ControlledTextarea } from '@jetstream/ui'; +import { ComboboxWithItems, ControlledTextarea, Grid, Textarea } from '@jetstream/ui'; import { FunctionComponent, useEffect, useState } from 'react'; import { isQueryValid } from 'soql-parser-js'; import { TransformationCriteria, TransformationOptions } from './mass-update-records.types'; @@ -22,7 +22,7 @@ export const MassUpdateRecordsObjectRowCriteria: FunctionComponent true, onOptionsChange, }) => { - const debouncedWhereClause = useDebounce(transformationOptions.whereClause, 300); + const debouncedWhereClause = useDebounce(transformationOptions?.whereClause || '', 300); const [whereClauseIsValid, setWhereClauseIsValid] = useState(true); useEffect(() => { diff --git a/apps/jetstream/src/app/components/shared/mass-update-records/useDeployRecords.ts b/apps/jetstream/src/app/components/shared/mass-update-records/useDeployRecords.ts index 72f32ec56..6f02b4d6f 100644 --- a/apps/jetstream/src/app/components/shared/mass-update-records/useDeployRecords.ts +++ b/apps/jetstream/src/app/components/shared/mass-update-records/useDeployRecords.ts @@ -80,7 +80,7 @@ export function useDeployRecords( const jobId = jobInfo.id || ''; const batches = splitArrayToMaxSize(records, batchSize).map((batch) => ({ records: batch, - csv: generateCsv(batch, { header: true, columns: fields }), + csv: generateCsv(batch, { header: true, columns: fields, delimiter: ',' }), })); deployResults.jobInfo = jobInfo; diff --git a/apps/jetstream/src/app/components/load-records/useDeployMetadataPackage.tsx b/apps/jetstream/src/app/components/shared/useDeployMetadataPackage.tsx similarity index 98% rename from apps/jetstream/src/app/components/load-records/useDeployMetadataPackage.tsx rename to apps/jetstream/src/app/components/shared/useDeployMetadataPackage.tsx index e1827b150..3ab7612c2 100644 --- a/apps/jetstream/src/app/components/load-records/useDeployMetadataPackage.tsx +++ b/apps/jetstream/src/app/components/shared/useDeployMetadataPackage.tsx @@ -95,7 +95,7 @@ function reducer(state: State, action: Action): State { * @param selectedOrg * @param changesetName */ -export function useDeployMetadataPackage(serverUrl: string, onFinished: () => void) { +export function useDeployMetadataPackage(serverUrl: string, onFinished?: () => void) { const isMounted = useRef(true); const rollbar = useRollbar(); const [{ hasLoaded, loading, hasError, errorMessage, deployId, results }, dispatch] = useReducer(reducer, { @@ -131,7 +131,7 @@ export function useDeployMetadataPackage(serverUrl: string, onFinished: () => vo }, }); dispatch({ type: 'SUCCESS', payload: { results } }); - onFinished(); + onFinished?.(); if (results.success) { notifyUser(`Deployment finished successfully`, { body: getNotificationMessageBody(results), @@ -143,6 +143,7 @@ export function useDeployMetadataPackage(serverUrl: string, onFinished: () => vo tag: 'deploy-package', }); } + return results; } } catch (ex) { logger.warn('[useDeployMetadataPackage][ERROR]', ex.message); @@ -152,7 +153,7 @@ export function useDeployMetadataPackage(serverUrl: string, onFinished: () => vo } } }, - [notifyUser] + [notifyUser, onFinished, rollbar] ); return { deployMetadata, results, deployId, hasLoaded, loading, lastChecked, hasError, errorMessage }; diff --git a/apps/jetstream/src/app/components/sobject-export/SObjectExport.tsx b/apps/jetstream/src/app/components/sobject-export/SObjectExport.tsx index 39b166980..a80ac274e 100644 --- a/apps/jetstream/src/app/components/sobject-export/SObjectExport.tsx +++ b/apps/jetstream/src/app/components/sobject-export/SObjectExport.tsx @@ -2,6 +2,7 @@ import { css } from '@emotion/react'; import { logger } from '@jetstream/shared/client-logger'; import { INDEXED_DB } from '@jetstream/shared/constants'; import { useRollbar } from '@jetstream/shared/ui-utils'; +import { SplitWrapper as Split } from '@jetstream/splitjs'; import { ListItem, MapOf, Maybe, SalesforceOrgUi } from '@jetstream/types'; import { AutoFullHeightContainer, @@ -25,7 +26,6 @@ import { import type { DescribeGlobalSObjectResult } from 'jsforce'; import localforage from 'localforage'; import { Fragment, FunctionComponent, useEffect, useRef, useState } from 'react'; -import { SplitWrapper as Split } from '@jetstream/splitjs'; import { useRecoilState, useRecoilValue } from 'recoil'; import { applicationCookieState, selectedOrgState } from '../../app-state'; import * as fromJetstreamEvents from '../core/jetstream-events'; @@ -160,7 +160,7 @@ export const SObjectExport: FunctionComponent = () => { } catch (ex) { logger.error(ex); setErrorMessage(ex.message); - rollbar.error('Error preparing sobject export', ex); + rollbar.error('Error preparing sobject export', { message: ex.message, stack: ex.stack }); } finally { setLoading(false); } diff --git a/apps/jetstream/src/app/components/update-records/deployment/MassUpdateRecordsDeployment.tsx b/apps/jetstream/src/app/components/update-records/deployment/MassUpdateRecordsDeployment.tsx index 532c00197..06198dd91 100644 --- a/apps/jetstream/src/app/components/update-records/deployment/MassUpdateRecordsDeployment.tsx +++ b/apps/jetstream/src/app/components/update-records/deployment/MassUpdateRecordsDeployment.tsx @@ -16,10 +16,10 @@ import { ChangeEvent, FunctionComponent, useCallback, useEffect, useState } from import { Link } from 'react-router-dom'; import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil'; import { selectedOrgState } from '../../../app-state'; -import * as fromMassUpdateState from '../mass-update-records.state'; import MassUpdateRecordsDeploymentRow from '../../shared/mass-update-records/MassUpdateRecordsDeploymentRow'; -import { useDeployRecords } from '../../shared/mass-update-records/useDeployRecords'; import { DeployResults, MetadataRow } from '../../shared/mass-update-records/mass-update-records.types'; +import { useDeployRecords } from '../../shared/mass-update-records/useDeployRecords'; +import * as fromMassUpdateState from '../mass-update-records.state'; const HEIGHT_BUFFER = 170; const MAX_BATCH_SIZE = 10000; @@ -140,7 +140,7 @@ export const MassUpdateRecordsDeployment: FunctionComponent ({ ((item: ListItem) => { return item.id; }) as any - ).map((item) => ({ ...item, label: item.value, secondaryLabel: null })); + ).map((item) => ({ ...item, label: item.value })); }, }); diff --git a/apps/jetstream/src/app/workers/jobs.worker.ts b/apps/jetstream/src/app/workers/jobs.worker.ts index 402bea3d7..be66320d9 100644 --- a/apps/jetstream/src/app/workers/jobs.worker.ts +++ b/apps/jetstream/src/app/workers/jobs.worker.ts @@ -41,6 +41,7 @@ import type { WorkerMessage, } from '@jetstream/types'; import type { Record } from 'jsforce'; +import clamp from 'lodash/clamp'; import isString from 'lodash/isString'; import { axiosElectronAdapter, initMessageHandler } from '../components/core/electron-axios-adapter'; @@ -79,13 +80,17 @@ async function handleMessage(name: AsyncJobType, payloadData: AsyncJobWorkerMess // TODO: add validation to ensure that we have at least one record // also, we are assuming that all records are same SObject const MAX_DELETE_RECORDS = 200; - let records: Record | Record[] = job.meta; // TODO: add strong type + + let { records, batchSize } = job.meta as { records: Record[]; batchSize?: number }; records = Array.isArray(records) ? records : [records]; + + batchSize = clamp(batchSize || MAX_DELETE_RECORDS, 1, 200); + const sobject = getSObjectFromRecordUrl(records[0].attributes.url); const allIds: string[] = records.map((record) => getIdFromRecordUrl(record.attributes.url)); const results: any[] = []; - for (const ids of splitArrayToMaxSize(allIds, MAX_DELETE_RECORDS)) { + for (const ids of splitArrayToMaxSize(allIds, batchSize)) { try { // TODO: add progress notification and allow cancellation let tempResults = await sobjectOperation(org, sobject, 'delete', { ids }, { allOrNone: false }); @@ -115,6 +120,7 @@ async function handleMessage(name: AsyncJobType, payloadData: AsyncJobWorkerMess fields, records, hasAllRecords, + includeDeletedRecords, useBulkApi, fileFormat, fileName, @@ -151,7 +157,7 @@ async function handleMessage(name: AsyncJobType, payloadData: AsyncJobWorkerMess } else { // Submit bulk query job and poll until results are ready // Main Browser context will handle downloading the file as a link so it can be streamed - const jobInfo = await bulkApiCreateJob(org, { type: 'QUERY', sObject }); + const jobInfo = await bulkApiCreateJob(org, { type: includeDeletedRecords ? 'QUERY_ALL' : 'QUERY', sObject }); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const jobId = jobInfo.id!; const batchResult = await bulkApiAddBatchToJob(org, jobId, soql, true); diff --git a/apps/jetstream/src/main.scss b/apps/jetstream/src/main.scss index ff44a8601..e71c056ed 100644 --- a/apps/jetstream/src/main.scss +++ b/apps/jetstream/src/main.scss @@ -158,8 +158,11 @@ pre { &:not(.read-only) { cursor: pointer; } + &.disabled { + cursor: not-allowed; + } margin: 1px; - &:not(.read-only):hover { + &:not(.disabled):not(.read-only):hover { background-color: $color-background; } &:not(.read-only).is-active { diff --git a/apps/jetstream/tsconfig.app.json b/apps/jetstream/tsconfig.app.json index 98101fc39..c4c2ffb3a 100644 --- a/apps/jetstream/tsconfig.app.json +++ b/apps/jetstream/tsconfig.app.json @@ -6,5 +6,5 @@ }, "exclude": ["**/*.spec.ts", "**/*.test.ts", "**/*.spec.tsx", "**/*.test.tsx", "jest.config.ts"], "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx", "../../custom-typings/index.d.ts"], - "files": ["../../node_modules/@nrwl/react/typings/cssmodule.d.ts", "../../node_modules/@nrwl/react/typings/image.d.ts"] + "files": ["../../node_modules/@nx/react/typings/cssmodule.d.ts", "../../node_modules/@nx/react/typings/image.d.ts"] } diff --git a/apps/jetstream/tsconfig.spec.json b/apps/jetstream/tsconfig.spec.json index 5f9f09407..460cd4de8 100644 --- a/apps/jetstream/tsconfig.spec.json +++ b/apps/jetstream/tsconfig.spec.json @@ -19,5 +19,5 @@ "jest.config.ts", "src/app/components/query/utils/__tests__/query-utils.data.ts" ], - "files": ["../../node_modules/@nrwl/react/typings/cssmodule.d.ts", "../../node_modules/@nrwl/react/typings/image.d.ts"] + "files": ["../../node_modules/@nx/react/typings/cssmodule.d.ts", "../../node_modules/@nx/react/typings/image.d.ts"] } diff --git a/apps/jetstream/tsconfig.workers.json b/apps/jetstream/tsconfig.workers.json index 51844e970..55b23f4a3 100644 --- a/apps/jetstream/tsconfig.workers.json +++ b/apps/jetstream/tsconfig.workers.json @@ -5,6 +5,6 @@ "types": [] }, "include": ["**/*.js/*.worker.ts"], - "files": ["../../node_modules/@nrwl/react/typings/cssmodule.d.ts", "../../node_modules/@nrwl/react/typings/image.d.ts"], + "files": ["../../node_modules/@nx/react/typings/cssmodule.d.ts", "../../node_modules/@nx/react/typings/image.d.ts"], "exclude": ["jest.config.ts"] } diff --git a/apps/jetstream/vite.config.ts b/apps/jetstream/vite.config.ts index c3de219d8..f95467e21 100644 --- a/apps/jetstream/vite.config.ts +++ b/apps/jetstream/vite.config.ts @@ -1,31 +1,16 @@ /// +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; import react from '@vitejs/plugin-react'; import dns from 'dns'; -import { PluginOption, defineConfig } from 'vite'; -import viteTsConfigPaths from 'vite-tsconfig-paths'; +import { defineConfig } from 'vite'; +import { baseHrefPlugin, replaceFiles } from './vite.plugins'; -dns.setDefaultResultOrder('verbatim'); -const BASE_HREF = '/app'; +// import replaceFiles from '@nx/vite/plugins/rollup-replace-files.plugin'; -/** - * Adds ` to the head of the index.html - * The reason why the `base` configuration property doesn't work is because it makes - * all assets served under `/app` of `/` and this impacts the download zip service worker - * We only want to the service worker to listen to events related to downloads, but not capture any other events - * and the only way to do this is make sure all assets are served from the root, but we still want our app path to be `/app` - * - * This mimics the same behavior we had with webpack before migrating to vite - */ -const baseHrefPlugin: () => PluginOption = () => { - return { - name: 'html-transform', - transformIndexHtml(html) { - return html.replace('', `\n `); - }, - }; -}; +dns.setDefaultResultOrder('verbatim'); export default defineConfig({ + root: __dirname, cacheDir: '../../node_modules/.vite/jetstream', envPrefix: 'NX', @@ -35,33 +20,32 @@ export default defineConfig({ }, build: { + outDir: '../../dist/apps/jetstream', + reportCompressedSize: true, + commonjsOptions: { transformMixedEsModules: true }, // Put all assets at the root of the app instead of under /assets assetsDir: './', - rollupOptions: { - output: { - sourcemap: true, - }, - }, + sourcemap: true, + emptyOutDir: true, + rollupOptions: {}, }, plugins: [ + replaceFiles([ + // { replace: 'apps/jetstream/src/environments/environment.ts', with: 'apps/jetstream/src/environments/environment.prod.ts' }, + // { replace: 'libs/ui/.storybook/storybook-styles.scss', with: 'apps/jetstream/src/main.scss' }, + ]), react({ jsxImportSource: '@emotion/react', babel: { plugins: ['@emotion/babel-plugin'], }, }), - viteTsConfigPaths({ - root: '../../', - }), + nxViteTsPaths(), baseHrefPlugin(), ], worker: { - plugins: [ - viteTsConfigPaths({ - root: '../../', - }), - ], + plugins: () => [nxViteTsPaths()], }, }); diff --git a/apps/jetstream/vite.plugins.ts b/apps/jetstream/vite.plugins.ts new file mode 100644 index 000000000..a33e19e18 --- /dev/null +++ b/apps/jetstream/vite.plugins.ts @@ -0,0 +1,75 @@ +import { PluginOption } from 'vite'; + +const BASE_HREF = '/app'; + +/** + * Adds ` to the head of the index.html + * The reason why the `base` configuration property doesn't work is because it makes + * all assets served under `/app` of `/` and this impacts the download zip service worker + * We only want to the service worker to listen to events related to downloads, but not capture any other events + * and the only way to do this is make sure all assets are served from the root, but we still want our app path to be `/app` + * + * This mimics the same behavior we had with webpack before migrating to vite + */ +export const baseHrefPlugin: () => PluginOption = () => { + return { + name: 'html-transform', + transformIndexHtml(html) { + return html.replace('', `\n `); + }, + }; +}; + +// NOTE: this is a copy of the plugin from @nx/vite - current included version (17.2.7) did not work +// copied from master which had additional fixes +// https://github.com/nrwl/nx/blob/master/packages/vite/plugins/rollup-replace-files.plugin.ts +/** + * @function replaceFiles + * @param {FileReplacement[]} replacements + * @return {({name: "rollup-plugin-replace-files", enforce: "pre" | "post" | undefined, Promise})} + */ +export function replaceFiles(replacements: FileReplacement[]): null | { + name: string; + enforce: 'pre' | 'post' | undefined; + resolveId(source: any, importer: any, options: any): Promise; +} { + if (!replacements?.length) { + return null; + } + return { + name: 'rollup-plugin-replace-files', + enforce: 'pre', + async resolveId(source, importer, options) { + const resolved = await this.resolve(source, importer, { + ...options, + skipSelf: true, + }); + /** + * The reason we're using endsWith here is because the resolved id + * will be the absolute path to the file. We want to check if the + * file ends with the file we're trying to replace, which will be essentially + * the path from the root of our workspace. + */ + + const foundReplace = replacements.find((replacement) => resolved?.id?.endsWith(replacement.replace)); + if (foundReplace) { + console.info(`replace "${foundReplace.replace}" with "${foundReplace.with}"`); + try { + // return new file content + return { + id: foundReplace.with, + }; + } catch (err) { + console.error(err); + return null; + } + } + return null; + }, + }; +} + +export interface FileReplacement { + replace: string; + with: string; +} diff --git a/apps/landing-e2e/.eslintrc.json b/apps/landing-e2e/.eslintrc.json deleted file mode 100644 index f5984484b..000000000 --- a/apps/landing-e2e/.eslintrc.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "rules": {}, - "overrides": [ - { - "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], - "parserOptions": { - "project": ["apps/landing-e2e/tsconfig.*?.json"] - }, - "rules": {} - }, - { - "files": ["src/plugins/index.js"], - "rules": { - "@typescript-eslint/no-var-requires": "off", - "no-undef": "off" - } - } - ], - "extends": ["../../.eslintrc.json", "plugin:cypress/recommended"], - "ignorePatterns": ["!**/*"] -} diff --git a/apps/landing-e2e/cypress.json b/apps/landing-e2e/cypress.json deleted file mode 100644 index 6b46082ca..000000000 --- a/apps/landing-e2e/cypress.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "fileServerFolder": ".", - "fixturesFolder": "./src/fixtures", - "integrationFolder": "./src/integration", - "modifyObstructiveCode": false, - "pluginsFile": "./src/plugins/index", - "supportFile": "./src/support/index.ts", - "video": true, - "videosFolder": "../../dist/cypress/apps/landing-e2e/videos", - "screenshotsFolder": "../../dist/cypress/apps/landing-e2e/screenshots", - "chromeWebSecurity": false -} diff --git a/apps/landing-e2e/project.json b/apps/landing-e2e/project.json deleted file mode 100644 index 47f0d219e..000000000 --- a/apps/landing-e2e/project.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "landing-e2e", - "$schema": "../../node_modules/nx/schemas/project-schema.json", - "sourceRoot": "apps/landing-e2e/src", - "projectType": "application", - "targets": { - "e2e": { - "executor": "@nrwl/cypress:cypress", - "options": { - "cypressConfig": "apps/landing-e2e/cypress.json", - "tsConfig": "apps/landing-e2e/tsconfig.e2e.json", - "devServerTarget": "landing:serve" - }, - "configurations": { - "production": { - "devServerTarget": "landing:serve:production" - } - } - }, - "lint": { - "executor": "@nrwl/linter:eslint", - "options": { - "lintFilePatterns": ["apps/landing-e2e/src/**/*.ts", "apps/landing-e2e/src/**/*.js"] - } - } - }, - "tags": [], - "implicitDependencies": ["landing"] -} diff --git a/apps/landing-e2e/src/fixtures/example.json b/apps/landing-e2e/src/fixtures/example.json deleted file mode 100644 index 294cbed6c..000000000 --- a/apps/landing-e2e/src/fixtures/example.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "Using fixtures to represent data", - "email": "hello@cypress.io" -} diff --git a/apps/landing-e2e/src/integration/app.spec.ts b/apps/landing-e2e/src/integration/app.spec.ts deleted file mode 100644 index 037830b8a..000000000 --- a/apps/landing-e2e/src/integration/app.spec.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { getNavLinks, getMainLinks, getFooterLinks } from '../support/app.po'; - -describe('landing', () => { - beforeEach(() => cy.visit('/')); - - it('Should have nav links on all pages', () => { - function validateNavLinks() { - getNavLinks().contains('Features').should('have.attr', 'href', '/#features'); - getNavLinks().contains('Documentation').should('have.attr', 'href', 'https://docs.getjetstream.app/'); - getNavLinks().contains('Contact Us').should('have.attr', 'href', 'mailto:support@getjetstream.app'); - getNavLinks().contains('Log in').should('have.attr', 'href', '/oauth/login'); - getNavLinks().contains('Sign-Up').should('have.attr', 'href', '/oauth/signup'); - } - - validateNavLinks(); - - cy.visit('/privacy/'); - validateNavLinks(); - - cy.visit('/terms-of-service/'); - validateNavLinks(); - }); - - it('Should have CTA sign up', () => { - getMainLinks().contains('Sign-up for a free account').should('have.attr', 'href', '/oauth/signup'); - }); - - it('Should have footer links', () => { - getFooterLinks().contains('Documentation').should('have.attr', 'href', 'https://docs.getjetstream.app/'); - getFooterLinks().contains('Contact Us').should('have.attr', 'href', 'mailto:support@getjetstream.app'); - getFooterLinks().contains('Privacy').should('have.attr', 'href', '/privacy/'); - getFooterLinks().contains('Terms').should('have.attr', 'href', '/terms-of-service/'); - }); - - it('Should navigate to privacy page', () => { - getFooterLinks().contains('Privacy').click(); - cy.url().should('include', '/privacy/'); - }); - - it('Should navigate to terms page', () => { - getFooterLinks().contains('Terms').click(); - cy.url().should('include', '/terms-of-service/'); - }); -}); diff --git a/apps/landing-e2e/src/plugins/index.js b/apps/landing-e2e/src/plugins/index.js deleted file mode 100644 index 40b146c23..000000000 --- a/apps/landing-e2e/src/plugins/index.js +++ /dev/null @@ -1,22 +0,0 @@ -// *********************************************************** -// This example plugins/index.js can be used to load plugins -// -// You can change the location of this file or turn off loading -// the plugins file with the 'pluginsFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/plugins-guide -// *********************************************************** - -// This function is called when a project is opened or re-opened (e.g. due to -// the project's config changing) - -const { preprocessTypescript } = require('@nrwl/cypress/plugins/preprocessor'); - -module.exports = (on, config) => { - // `on` is used to hook into various events Cypress emits - // `config` is the resolved Cypress config - // Preprocess Typescript file using Nx helper - config.env.auth0_username = 'paustint+cypress-test@gmail.com'; // process.env.AUTH0_USERNAME - config.env.auth0_password = 'Password123'; // process.env.AUTH0_PASSWORD -}; diff --git a/apps/landing-e2e/src/support/app.po.ts b/apps/landing-e2e/src/support/app.po.ts deleted file mode 100644 index 4e740efa4..000000000 --- a/apps/landing-e2e/src/support/app.po.ts +++ /dev/null @@ -1,15 +0,0 @@ -export const getNavLinks = () => cy.get('nav a'); -export const getMainLinks = () => cy.get('main a'); -export const getFooterLinks = () => cy.get('footer a'); - -// export const getFeaturesLink = () => cy.get('nav div div:nth-child(2) a:nth-child(1)'); -// export const getDocLink = () => cy.get('nav div div:nth-child(2) a:nth-child(2)'); -// export const getContactUsLink = () => cy.get('nav div div:nth-child(2) a:nth-child(3)'); -// export const getLoginLink = () => cy.get('a[href="/oauth/login"]'); -// export const getSignUpCtaLink = () => cy.get('a[href="/oauth/signup"]').first(); -// export const getSignUpLink = () => cy.get('a[href="/oauth/signup"]').last(); -// export const getPrivacyLink = () => cy.get('footer > div > div > div > div:nth-child(2) ul > li:nth-child(1) > a'); -// export const getTermsLink = () => cy.get('footer > div > div > div > div:nth-child(2) ul > li:nth-child(2) > a'); - -// export const getDocFooterLink = () => cy.get('a'); -// export const getContactUsFooterLink = () => cy.get('a'); diff --git a/apps/landing-e2e/src/support/commands.ts b/apps/landing-e2e/src/support/commands.ts deleted file mode 100644 index 61b3a3e35..000000000 --- a/apps/landing-e2e/src/support/commands.ts +++ /dev/null @@ -1,31 +0,0 @@ -// *********************************************** -// This example commands.js shows you how to -// create various custom commands and overwrite -// existing commands. -// -// For more comprehensive examples of custom -// commands please read more here: -// https://on.cypress.io/custom-commands -// *********************************************** -// eslint-disable-next-line @typescript-eslint/no-namespace -declare namespace Cypress { - interface Chainable { - login(email: string, password: string): void; - } -} -// -// -- This is a parent command -- -Cypress.Commands.add('login', (email, password) => { - console.log('Custom command example: Login', email, password); -}); -// -// -- This is a child command -- -// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) -// -// -// -- This is a dual command -- -// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) -// -// -// -- This will overwrite an existing command -- -// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) diff --git a/apps/landing-e2e/src/support/index.ts b/apps/landing-e2e/src/support/index.ts deleted file mode 100644 index 3d469a6b6..000000000 --- a/apps/landing-e2e/src/support/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -// *********************************************************** -// This example support/index.js is processed and -// loaded automatically before your test files. -// -// This is a great place to put global configuration and -// behavior that modifies Cypress. -// -// You can change the location of this file or turn off -// automatically serving support files with the -// 'supportFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/configuration -// *********************************************************** - -// Import commands.js using ES2015 syntax: -import './commands'; diff --git a/apps/landing-e2e/tsconfig.e2e.json b/apps/landing-e2e/tsconfig.e2e.json deleted file mode 100644 index f347341bd..000000000 --- a/apps/landing-e2e/tsconfig.e2e.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "sourceMap": false, - "outDir": "../../dist/out-tsc", - "allowJs": true - }, - "include": ["src/**/*.ts", "src/**/*.js"] -} diff --git a/apps/landing/.babelrc b/apps/landing/.babelrc deleted file mode 100644 index c7d82affe..000000000 --- a/apps/landing/.babelrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "presets": ["@nrwl/next/babel"], - "plugins": [] -} diff --git a/apps/landing/.eslintrc.json b/apps/landing/.eslintrc.json index afa39de07..c53eae6e1 100644 --- a/apps/landing/.eslintrc.json +++ b/apps/landing/.eslintrc.json @@ -274,7 +274,7 @@ } }, "plugins": ["import", "jsx-a11y", "react", "react-hooks"], - "extends": ["plugin:@nrwl/nx/react-typescript", "../../.eslintrc.json", "next", "next/core-web-vitals"], + "extends": ["plugin:@nx/react-typescript", "../../.eslintrc.json", "next", "next/core-web-vitals"], "ignorePatterns": ["!**/*"], "overrides": [ { diff --git a/apps/landing/components/FigureImg.tsx b/apps/landing/components/FigureImg.tsx index d636a56bb..3dce3d12f 100644 --- a/apps/landing/components/FigureImg.tsx +++ b/apps/landing/components/FigureImg.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +/* eslint-disable @next/next/no-img-element */ export interface FigureImgProps { src: string; diff --git a/apps/landing/components/FigureImgWithViewFullScreen.tsx b/apps/landing/components/FigureImgWithViewFullScreen.tsx index 05dfacaef..867117665 100644 --- a/apps/landing/components/FigureImgWithViewFullScreen.tsx +++ b/apps/landing/components/FigureImgWithViewFullScreen.tsx @@ -1,4 +1,5 @@ -import React, { Fragment, useState } from 'react'; +/* eslint-disable @next/next/no-img-element */ +import { Fragment, useState } from 'react'; import { FigureImgProps } from './FigureImg'; import Modal from './Modal'; diff --git a/apps/landing/components/Footer.tsx b/apps/landing/components/Footer.tsx index bac73ef5d..14b45cfca 100644 --- a/apps/landing/components/Footer.tsx +++ b/apps/landing/components/Footer.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @next/next/no-img-element */ import Link from 'next/link'; const footerNavigation = { @@ -39,7 +40,7 @@ export const Footer = ({ omitLinks = [] }: { omitLinks?: string[] }) => (

    Support

    -
      +
        {footerNavigation.support .filter((item) => !omitLinks.includes(item.href)) .map((item) => ( @@ -61,7 +62,7 @@ export const Footer = ({ omitLinks = [] }: { omitLinks?: string[] }) => (

    Company

    -
      +
        {footerNavigation.company .filter((item) => !omitLinks.includes(item.href)) .map((item) => ( @@ -75,7 +76,7 @@ export const Footer = ({ omitLinks = [] }: { omitLinks?: string[] }) => (

    Legal

    -
      +
        {footerNavigation.legal .filter((item) => !omitLinks.includes(item.href)) .map((item) => ( diff --git a/apps/landing/components/HeaderNoNavigation.tsx b/apps/landing/components/HeaderNoNavigation.tsx index 21b656b83..35303dd7f 100644 --- a/apps/landing/components/HeaderNoNavigation.tsx +++ b/apps/landing/components/HeaderNoNavigation.tsx @@ -1,5 +1,5 @@ +/* eslint-disable @next/next/no-img-element */ import Link from 'next/link'; -import React from 'react'; export const HeaderNoNavigation = () => (
        diff --git a/apps/landing/components/Navigation.tsx b/apps/landing/components/Navigation.tsx index 10e344b58..8a266dc4c 100644 --- a/apps/landing/components/Navigation.tsx +++ b/apps/landing/components/Navigation.tsx @@ -1,8 +1,9 @@ +/* eslint-disable @next/next/no-img-element */ import { Popover, Transition } from '@headlessui/react'; import { MenuIcon, XIcon } from '@heroicons/react/outline'; import classNames from 'classnames'; import Link from 'next/link'; -import React, { Fragment } from 'react'; +import { Fragment } from 'react'; const navigation = [ { name: 'Features', href: '/#features' }, diff --git a/apps/landing/components/blog-post-renderers.tsx b/apps/landing/components/blog-post-renderers.tsx index d664c0743..405240cd2 100644 --- a/apps/landing/components/blog-post-renderers.tsx +++ b/apps/landing/components/blog-post-renderers.tsx @@ -17,21 +17,22 @@ const getNodeText = (node: ReactNode) => { return node.map(getNodeText).join(''); } if (typeof node === 'object' && node) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any return getNodeText((node as any).props?.children); } }; -function getSlug(node): string | undefined { - try { - const text = getNodeText(node) as string; - if (isString(text)) { - return text.toLowerCase().replace(NON_URL_CHARACTERS, '-'); - } - } catch (ex) { - // could not process id - } - return undefined; -} +// function getSlug(node): string | undefined { +// try { +// const text = getNodeText(node) as string; +// if (isString(text)) { +// return text.toLowerCase().replace(NON_URL_CHARACTERS, '-'); +// } +// } catch (ex) { +// // could not process id +// } +// return undefined; +// } const wrapHeadingWithAnchor = (type: 'heading-1' | 'heading-2' | 'heading-3' | 'heading-4' | 'heading-5' | 'heading-6'): NodeRenderer => { return (node, children) => { @@ -114,10 +115,10 @@ export function renderBlogPostRichText(richText: Document) { ), [BLOCKS.UL_LIST]: (node, children) => { - return
          {children}
        ; + return
          {children}
        ; }, [BLOCKS.OL_LIST]: (node, children) => { - return
          {children}
        ; + return
          {children}
        ; }, [BLOCKS.HEADING_1]: wrapHeadingWithAnchor('heading-1'), [BLOCKS.HEADING_2]: wrapHeadingWithAnchor('heading-2'), diff --git a/apps/landing/components/new/AnalyticsSummary.tsx b/apps/landing/components/new/AnalyticsSummary.tsx new file mode 100644 index 000000000..aa3498470 --- /dev/null +++ b/apps/landing/components/new/AnalyticsSummary.tsx @@ -0,0 +1,25 @@ +import { AnalyticStat } from '@jetstream/types'; + +export default function AnalyticsSummary({ stats }: { stats: AnalyticStat[] }) { + return ( +
        +
        +
        +
        +

        + Trusted by thousands of Salesforce professionals worldwide +

        +
        +
        + {stats.map((stat) => ( +
        +
        {stat.name}
        +
        {stat.value}
        +
        + ))} +
        +
        +
        +
        + ); +} diff --git a/apps/landing/components/new/ConnectWithTeam.tsx b/apps/landing/components/new/ConnectWithTeam.tsx index 335ff1274..f95c4df58 100644 --- a/apps/landing/components/new/ConnectWithTeam.tsx +++ b/apps/landing/components/new/ConnectWithTeam.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +/* eslint-disable @next/next/no-img-element */ const items = [ { diff --git a/apps/landing/components/new/FeatureScreenshot.tsx b/apps/landing/components/new/FeatureScreenshot.tsx index d9fabb01a..d87354ee6 100644 --- a/apps/landing/components/new/FeatureScreenshot.tsx +++ b/apps/landing/components/new/FeatureScreenshot.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +/* eslint-disable @next/next/no-img-element */ import FeatureHeading from './FeatureHeading'; export const FeatureScreenshot = () => ( diff --git a/apps/landing/components/new/HeaderCta.tsx b/apps/landing/components/new/HeaderCta.tsx index 805519155..1b9f4c6ba 100644 --- a/apps/landing/components/new/HeaderCta.tsx +++ b/apps/landing/components/new/HeaderCta.tsx @@ -1,14 +1,14 @@ -import React from 'react'; +/* eslint-disable @next/next/no-img-element */ import { HeartIcon } from '@heroicons/react/solid'; export const HeaderCta = () => ( -
        +
        - Jetstream is open source and free to use + Jetstream is community supported and free to use

        A better way to @@ -30,12 +30,9 @@ export const HeaderCta = () => (

        - Jetstream is open source and free to use. + Jetstream is free to use thanks to the support of our community.

        <> -

        - Costs are covered by the developer - donations and sponsorships are greatly appreciated via Github sponsors. -

        ( rel="noreferrer" >
        -
        +
        {/* Illustration taken from Lucid Illustrations: https://lucid.pixsellz.io/ */} ( -
        -
        -
        -
        - - -
        -
        - -
        -
        -

        - Start your free 14-day trial, no credit card necessary. By providing your email, you agree to our{' '} - - terms or service - - . -

        -
        -
        -); - -export default HeaderCtaFreeTrial; diff --git a/apps/landing/components/new/LandingPage.tsx b/apps/landing/components/new/LandingPage.tsx index 93aee814b..d0b85729b 100644 --- a/apps/landing/components/new/LandingPage.tsx +++ b/apps/landing/components/new/LandingPage.tsx @@ -1,3 +1,5 @@ +import { AnalyticStat } from '@jetstream/types'; +import AnalyticsSummary from './AnalyticsSummary'; import ConnectWithTeam from './ConnectWithTeam'; import FeatureGrid from './FeatureGrid'; import FeatureScreenshot from './FeatureScreenshot'; @@ -6,9 +8,10 @@ import Learn from './Learn'; import SupportCta from './SupportCta'; import Testimonial from './Testimonial'; -export const LandingPage = () => ( +export const LandingPage = ({ stats }: { stats: AnalyticStat[] }) => (
        + diff --git a/apps/landing/components/new/Learn.tsx b/apps/landing/components/new/Learn.tsx index 2452bbaa8..57bf23390 100644 --- a/apps/landing/components/new/Learn.tsx +++ b/apps/landing/components/new/Learn.tsx @@ -1,8 +1,3 @@ -import React from 'react'; - -{ - /* FIXME: upsplash logo not allowed */ -} const blogPosts = [ { id: 1, @@ -70,7 +65,7 @@ export const Learn = () => (

        Helpful Resources

        Check out the{' '} - + Jetstream documentation {' '} for more information about using Jetstream. @@ -85,7 +80,7 @@ export const Learn = () => (

        {post.category.name}

        - +

        {post.title}

        {post.preview}

        diff --git a/apps/landing/components/new/SupportCta.tsx b/apps/landing/components/new/SupportCta.tsx index be9bfee55..7e1dda319 100644 --- a/apps/landing/components/new/SupportCta.tsx +++ b/apps/landing/components/new/SupportCta.tsx @@ -1,5 +1,4 @@ -import React from 'react'; - +/* eslint-disable @next/next/no-img-element */ const items = [ { image: 'https://res.cloudinary.com/getjetstream/image/upload/v1673822085/public/discord-mark-black_yj4q38.svg', @@ -43,7 +42,7 @@ export const SupportCta = () => (
        -

        We’re here to help

        +

        We're here to help

        Have a question about Jetstream or need support?

        diff --git a/apps/landing/jest.config.ts b/apps/landing/jest.config.ts index d70419cf4..05930a225 100644 --- a/apps/landing/jest.config.ts +++ b/apps/landing/jest.config.ts @@ -1,7 +1,7 @@ /* eslint-disable */ export default { transform: { - '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest', + '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest', '^.+\\.[tj]sx?$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'], diff --git a/apps/landing/next.config.js b/apps/landing/next.config.js index 2d6d765f6..9ced2a460 100644 --- a/apps/landing/next.config.js +++ b/apps/landing/next.config.js @@ -1,15 +1,22 @@ -const withImages = require('next-images'); +const { composePlugins, withNx } = require('@nx/next'); -module.exports = withImages({ +/** + * @type {import('@nx/next/plugins/with-nx').WithNxOptions} + **/ +const nextConfig = { trailingSlash: true, - // #LAME https://github.com/nrwl/nx/issues/4182 - webpack(config) { - // Prevent nx from adding an svg handler - stick to what is provided by - // nextjs or that we have defined ourselves. - config.module.rules.push = (...items) => { - Array.prototype.push.call(config.module.rules, ...items.filter((item) => item.test.toString() !== '/\\.svg$/')); - }; - - return config; + nx: { + // Set this to true if you would like to use SVGR + // See: https://github.com/gregberge/svgr + svgr: false, }, -}); + output: 'export', + distDir: '../../dist/apps/landing', +}; + +const plugins = [ + // Add more Next.js plugins to this list if needed. + withNx, +]; + +module.exports = composePlugins(...plugins)(nextConfig); diff --git a/apps/landing/pages/_app.js b/apps/landing/pages/_app.js index 7e1723d8f..7ccd38628 100644 --- a/apps/landing/pages/_app.js +++ b/apps/landing/pages/_app.js @@ -1,4 +1,3 @@ -import React from 'react'; import './index.scss'; // This default export is required in a new `pages/_app.js` file. diff --git a/apps/landing/pages/about/index.tsx b/apps/landing/pages/about/index.tsx index 8ece6fa1d..fc03a099b 100644 --- a/apps/landing/pages/about/index.tsx +++ b/apps/landing/pages/about/index.tsx @@ -51,7 +51,7 @@ function About({ blogPosts }: PostProps) { About Jetstream

        - Jetstream is an open source project that was created and run by{' '} + Jetstream is a source-available project created and maintained{' '} - support@getjetstream.app + {email} {' '} if you have any questions or have feedback on what we can do better.

        diff --git a/apps/landing/pages/index.tsx b/apps/landing/pages/index.tsx index afda0a05c..2f599ac5e 100644 --- a/apps/landing/pages/index.tsx +++ b/apps/landing/pages/index.tsx @@ -1,11 +1,12 @@ +import { AnalyticStat } from '@jetstream/types'; +import { GetStaticProps, InferGetStaticPropsType } from 'next'; import Head from 'next/head'; -import React from 'react'; import Footer from '../components/Footer'; import Navigation from '../components/Navigation'; import LandingPage from '../components/new/LandingPage'; -import { fetchBlogPosts } from '../utils/data'; +import { fetchBlogPosts, getAnalyticSummary } from '../utils/data'; -export const Index = ({ omitBlogPosts }: { omitBlogPosts: boolean }) => { +export const Index = ({ stats, omitBlogPosts }: InferGetStaticPropsType) => { return (
        @@ -61,7 +62,7 @@ export const Index = ({ omitBlogPosts }: { omitBlogPosts: boolean }) => {
        - +
        @@ -70,9 +71,13 @@ export const Index = ({ omitBlogPosts }: { omitBlogPosts: boolean }) => { }; // This also gets called at build time -export async function getStaticProps({ params }) { +export const getStaticProps: GetStaticProps<{ + stats: AnalyticStat[]; + omitBlogPosts: boolean; +}> = async () => { + const stats = await getAnalyticSummary(); const blogPostsWithRelated = await fetchBlogPosts(); - return { props: { omitBlogPosts: Object.values(blogPostsWithRelated || {}).length === 0 } }; -} + return { props: { stats, omitBlogPosts: Object.values(blogPostsWithRelated || {}).length === 0 } }; +}; export default Index; diff --git a/apps/landing/pages/oauth-link/index.tsx b/apps/landing/pages/oauth-link/index.tsx index 5e9baac63..7a80b0db4 100644 --- a/apps/landing/pages/oauth-link/index.tsx +++ b/apps/landing/pages/oauth-link/index.tsx @@ -1,5 +1,5 @@ import Head from 'next/head'; -import React, { Fragment, useEffect, useState } from 'react'; +import { Fragment, useEffect, useState } from 'react'; import Footer from '../../components/Footer'; import HeaderNoNavigation from '../../components/HeaderNoNavigation'; import { parseQueryString } from '../../utils/utils'; @@ -136,7 +136,7 @@ function LinkAuthAccount() { target="_blank" rel="noreferrer" > - support@getjetstream.app + {email} .

        diff --git a/apps/landing/pages/subprocessors/index.tsx b/apps/landing/pages/subprocessors/index.tsx index 12e17460d..f16ca96a3 100644 --- a/apps/landing/pages/subprocessors/index.tsx +++ b/apps/landing/pages/subprocessors/index.tsx @@ -1,5 +1,5 @@ import Head from 'next/head'; -import React, { Fragment } from 'react'; +import { Fragment } from 'react'; import Footer from '../../components/Footer'; import Navigation from '../../components/Navigation'; @@ -29,43 +29,42 @@ const webSubProcessors = [ { name: 'Rollbar', function: 'Automated bug tracking', location: 'United States', optional: 'No' }, { name: 'Salesforce.com', function: 'Application core', location: 'United States', optional: 'No' }, ]; -const desktopSubProcessors = [ - { - name: 'Amplitude', - function: 'Telemetry', - location: 'United States', - optional: 'Yes, this is opt-in and you can turn off on first run.', - }, - { - name: 'Cloudinary', - function: 'Image hosting', - location: 'United States', - optional: 'Yes, this processor is only used if you add images while creating a support ticket.', - }, - { - name: 'Github', - function: 'Ticket tracking', - location: 'United States', - optional: 'Yes, this processor is only used if you file a support ticket.', - }, - { - name: 'Google', - function: 'File storage', - location: 'United States', - optional: 'File storage is opt-in only if you use Google Drive to save and read files.', - }, - { name: 'Mailgun', function: 'Email', location: 'United States', optional: 'No' }, - { - name: 'Rollbar', - function: 'Automated bug tracking', - location: 'United States', - optional: 'Yes, this is opt-in and you can turn off on first run.', - }, - { name: 'Salesforce.com', function: 'Application core', location: 'United States', optional: 'No' }, -]; +// const desktopSubProcessors = [ +// { +// name: 'Amplitude', +// function: 'Telemetry', +// location: 'United States', +// optional: 'Yes, this is opt-in and you can turn off on first run.', +// }, +// { +// name: 'Cloudinary', +// function: 'Image hosting', +// location: 'United States', +// optional: 'Yes, this processor is only used if you add images while creating a support ticket.', +// }, +// { +// name: 'Github', +// function: 'Ticket tracking', +// location: 'United States', +// optional: 'Yes, this processor is only used if you file a support ticket.', +// }, +// { +// name: 'Google', +// function: 'File storage', +// location: 'United States', +// optional: 'File storage is opt-in only if you use Google Drive to save and read files.', +// }, +// { name: 'Mailgun', function: 'Email', location: 'United States', optional: 'No' }, +// { +// name: 'Rollbar', +// function: 'Automated bug tracking', +// location: 'United States', +// optional: 'Yes, this is opt-in and you can turn off on first run.', +// }, +// { name: 'Salesforce.com', function: 'Application core', location: 'United States', optional: 'No' }, +// ]; function Privacy() { - const email = 'support@getjetstream.app'; return ( diff --git a/apps/landing/postcss.config.js b/apps/landing/postcss.config.js index 7ba8d9862..a0a2c8328 100644 --- a/apps/landing/postcss.config.js +++ b/apps/landing/postcss.config.js @@ -4,7 +4,7 @@ module.exports = { plugins: { 'postcss-import': {}, tailwindcss: { - config: './apps/landing/tailwind.config.js', + config: './tailwind.config.js', }, autoprefixer: {}, 'postcss-preset-env': { stage: 2 }, diff --git a/apps/landing/project.json b/apps/landing/project.json index d642c119d..91dc0a36c 100644 --- a/apps/landing/project.json +++ b/apps/landing/project.json @@ -3,67 +3,13 @@ "$schema": "../../node_modules/nx/schemas/project-schema.json", "sourceRoot": "apps/landing", "projectType": "application", - "generators": {}, "targets": { - "build": { - "executor": "@nrwl/next:build", - "options": { - "root": "apps/landing", - "outputPath": "dist/apps/landing" - }, - "configurations": { - "production": {}, - "development": { - "outputPath": "apps/landing" - } - }, - "outputs": ["{options.outputPath}"], - "defaultConfiguration": "production" - }, - "serve": { - "executor": "@nrwl/next:server", - "options": { - "buildTarget": "landing:build", - "port": 4300, - "dev": true - }, - "configurations": { - "production": { - "buildTarget": "landing:build:production", - "dev": false - }, - "development": { - "buildTarget": "landing:build:development", - "dev": true - } - }, - "defaultConfiguration": "development" - }, - "export": { - "executor": "@nrwl/next:export", - "options": { - "buildTarget": "landing:build:production" - } - }, - "lint": { - "executor": "@nrwl/linter:eslint", - "options": { - "lintFilePatterns": [ - "apps/landing/**/*.spec.ts", - "apps/landing/**/*.spec.tsx", - "apps/landing/**/*.spec.js", - "apps/landing/**/*.spec.jsx", - "apps/landing/**/*.d.ts" - ] - } - }, "test": { - "executor": "@nrwl/jest:jest", + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], "options": { - "jestConfig": "apps/landing/jest.config.ts", - "passWithNoTests": true - }, - "outputs": ["{workspaceRoot}/coverage/apps/landing"] + "jestConfig": "apps/landing/jest.config.ts" + } } }, "tags": [] diff --git a/apps/landing/tailwind.config.js b/apps/landing/tailwind.config.js index 6ea47fe5c..67bd8d840 100644 --- a/apps/landing/tailwind.config.js +++ b/apps/landing/tailwind.config.js @@ -1,6 +1,6 @@ const colors = require('tailwindcss/colors'); const defaultTheme = require('tailwindcss/defaultTheme'); -const { createGlobPatternsForDependencies } = require('@nrwl/next/tailwind'); +const { createGlobPatternsForDependencies } = require('@nx/next/tailwind'); const { join } = require('path'); module.exports = { diff --git a/apps/landing/tsconfig.json b/apps/landing/tsconfig.json index 964c443bb..4a1bc7986 100644 --- a/apps/landing/tsconfig.json +++ b/apps/landing/tsconfig.json @@ -5,16 +5,17 @@ "allowJs": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, - "types": ["node", "jest"], + "types": ["node", "jest", "google.accounts", "google.picker", "gapi.auth2", "gapi.client.drive"], "strict": false, "forceConsistentCasingInFileNames": true, "noEmit": true, "resolveJsonModule": true, "isolatedModules": true, "strictNullChecks": true, - "incremental": true + "incremental": true, + "plugins": [{ "name": "next" }] }, - "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"], + "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "next-env.d.ts"], "files": [], "exclude": ["node_modules", "jest.config.ts", "**/*.spec.ts", "**/*.test.ts"] } diff --git a/apps/landing/utils/data.ts b/apps/landing/utils/data.ts index ef9caa1aa..01f696191 100644 --- a/apps/landing/utils/data.ts +++ b/apps/landing/utils/data.ts @@ -1,8 +1,87 @@ +import { AnalyticStat } from '@jetstream/types'; import { createClient } from 'contentful'; +import numeral from 'numeral'; import { AuthorsById, BlogPost, BlogPostsBySlug, ContentfulBlogPostField, ContentfulIncludes } from './types'; +/** + * EVERYTHING IN THIS FILE MUST ONLY BE USED AT BUILD TIME + * DO NOT EXPORT ANYTHING WITH A RUNTIME DEPENDENCY + * ONLY CALL THESE FUNCTIONS WITHIN getStaticProps OR getServerSideProps + */ + let blogPostsBySlug: BlogPostsBySlug; +export async function getAnalyticSummary(): Promise { + const FALLBACK_SUMMARY = { + LOAD_SUMMARY: { + id: 'PLACEHOLDER-LOAD_SUMMARY', + month: 39980754, + week: 8156954, + year: 382287645, + type: 'LOAD_SUMMARY', + updatedAt: new Date(), + }, + QUERY_SUMMARY: { + id: 'PLACEHOLDER-QUERY_SUMMARY', + month: 50549, + week: 10425, + year: 334098, + type: 'QUERY_SUMMARY', + updatedAt: new Date(), + }, + }; + + // FIXME: this should call Amplitude API instead of storing/getting from DB + let results = FALLBACK_SUMMARY; + if (!process.env.CI) { + try { + results = await import('@prisma/client').then(({ PrismaClient }) => { + return new PrismaClient({ log: ['info'] }).analyticsSummary.findMany().then((result) => + result.reduce((acc, item) => { + acc[item.type] = item; + return acc; + }, FALLBACK_SUMMARY) + ); + }); + } catch (ex) { + console.log('Error fetching analytics summary - using fallback', ex); + } + } + + const summaryStats: AnalyticStat[] = [ + { + id: `${results.LOAD_SUMMARY.id}-year`, + name: 'Records loaded in past year', + value: `${numeral(results.LOAD_SUMMARY.year).format('0a').toUpperCase()}+`, + valueRaw: results.LOAD_SUMMARY.year, + lastUpdated: (results.LOAD_SUMMARY?.updatedAt || new Date()).toISOString(), + }, + { + id: `${results.LOAD_SUMMARY.id}-month`, + name: 'Records loaded in past month', + value: `${numeral(results.LOAD_SUMMARY.month).format('0a').toUpperCase()}+`, + valueRaw: results.LOAD_SUMMARY.month, + lastUpdated: (results.LOAD_SUMMARY?.updatedAt || new Date()).toISOString(), + }, + { + id: `${results.QUERY_SUMMARY.id}-year`, + name: 'Queries executed in past year', + value: `${numeral(results.QUERY_SUMMARY.year).format('0a').toUpperCase()}+`, + valueRaw: results.QUERY_SUMMARY.year, + lastUpdated: (results.QUERY_SUMMARY?.updatedAt || new Date()).toISOString(), + }, + { + id: `${results.QUERY_SUMMARY.id}-month`, + name: 'Queries executed in past month', + value: `${numeral(results.QUERY_SUMMARY.month).format('0a').toUpperCase()}+`, + valueRaw: results.QUERY_SUMMARY.month, + lastUpdated: (results.QUERY_SUMMARY?.updatedAt || new Date()).toISOString(), + }, + ]; + + return summaryStats; +} + export async function fetchBlogPosts() { if (blogPostsBySlug) { return blogPostsBySlug; diff --git a/apps/landing/utils/types.ts b/apps/landing/utils/types.ts index 544dd2643..609d8a4cf 100644 --- a/apps/landing/utils/types.ts +++ b/apps/landing/utils/types.ts @@ -1,6 +1,13 @@ import { Document } from '@contentful/rich-text-types'; import { Asset, EntryFields, Sys } from 'contentful'; +export interface AnalyticSummaryItem { + type: 'LOAD_SUMMARY' | 'QUERY_SUMMARY'; + week: number; + month: number; + year: number; +} + export interface ContentfulBlogPost { sys: Sys; fields: Array; diff --git a/apps/ui-e2e/.eslintrc.json b/apps/ui-e2e/.eslintrc.json deleted file mode 100644 index f305fac96..000000000 --- a/apps/ui-e2e/.eslintrc.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "rules": {}, - "overrides": [ - { - "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], - "parserOptions": { - "project": ["apps/ui-e2e/tsconfig.*?.json"] - }, - "rules": {} - }, - { - "files": ["src/plugins/index.js"], - "rules": { - "@typescript-eslint/no-var-requires": "off", - "no-undef": "off" - } - } - ], - "extends": ["../../.eslintrc.json", "plugin:cypress/recommended"], - "ignorePatterns": ["!**/*"] -} diff --git a/apps/ui-e2e/cypress.json b/apps/ui-e2e/cypress.json deleted file mode 100644 index 99a742470..000000000 --- a/apps/ui-e2e/cypress.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "fileServerFolder": ".", - "fixturesFolder": "./src/fixtures", - "integrationFolder": "./src/integration", - "modifyObstructiveCode": false, - "pluginsFile": "./src/plugins/index", - "supportFile": "./src/support/index.ts", - "video": true, - "videosFolder": "../../dist/cypress/apps/ui-e2e/videos", - "screenshotsFolder": "../../dist/cypress/apps/ui-e2e/screenshots", - "chromeWebSecurity": false, - "baseUrl": "http://localhost:4400" -} diff --git a/apps/ui-e2e/project.json b/apps/ui-e2e/project.json deleted file mode 100644 index 93cc24236..000000000 --- a/apps/ui-e2e/project.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "ui-e2e", - "$schema": "../../node_modules/nx/schemas/project-schema.json", - "sourceRoot": "apps/ui-e2e/src", - "projectType": "application", - "targets": { - "e2e": { - "executor": "@nrwl/cypress:cypress", - "options": { - "cypressConfig": "apps/ui-e2e/cypress.json", - "tsConfig": "apps/ui-e2e/tsconfig.e2e.json", - "devServerTarget": "ui:storybook" - }, - "configurations": { - "ci": { - "devServerTarget": "ui:storybook:ci" - } - } - }, - "lint": { - "executor": "@nrwl/linter:eslint", - "options": { - "lintFilePatterns": ["apps/ui-e2e/src/**/*.ts", "apps/ui-e2e/src/**/*.js"] - } - } - }, - "tags": [], - "implicitDependencies": ["ui"] -} diff --git a/apps/ui-e2e/src/fixtures/example.json b/apps/ui-e2e/src/fixtures/example.json deleted file mode 100644 index 294cbed6c..000000000 --- a/apps/ui-e2e/src/fixtures/example.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "Using fixtures to represent data", - "email": "hello@cypress.io" -} diff --git a/apps/ui-e2e/src/plugins/index.js b/apps/ui-e2e/src/plugins/index.js deleted file mode 100644 index 5b06a6c11..000000000 --- a/apps/ui-e2e/src/plugins/index.js +++ /dev/null @@ -1,20 +0,0 @@ -// *********************************************************** -// This example plugins/index.js can be used to load plugins -// -// You can change the location of this file or turn off loading -// the plugins file with the 'pluginsFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/plugins-guide -// *********************************************************** - -// This function is called when a project is opened or re-opened (e.g. due to -// the project's config changing) - -const { preprocessTypescript } = require('@nrwl/cypress/plugins/preprocessor'); - -module.exports = (on, config) => { - // `on` is used to hook into various events Cypress emits - // `config` is the resolved Cypress config - // Preprocess Typescript file using Nx helper -}; diff --git a/apps/ui-e2e/src/support/commands.ts b/apps/ui-e2e/src/support/commands.ts deleted file mode 100644 index 61b3a3e35..000000000 --- a/apps/ui-e2e/src/support/commands.ts +++ /dev/null @@ -1,31 +0,0 @@ -// *********************************************** -// This example commands.js shows you how to -// create various custom commands and overwrite -// existing commands. -// -// For more comprehensive examples of custom -// commands please read more here: -// https://on.cypress.io/custom-commands -// *********************************************** -// eslint-disable-next-line @typescript-eslint/no-namespace -declare namespace Cypress { - interface Chainable { - login(email: string, password: string): void; - } -} -// -// -- This is a parent command -- -Cypress.Commands.add('login', (email, password) => { - console.log('Custom command example: Login', email, password); -}); -// -// -- This is a child command -- -// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) -// -// -// -- This is a dual command -- -// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) -// -// -// -- This will overwrite an existing command -- -// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) diff --git a/apps/ui-e2e/src/support/index.ts b/apps/ui-e2e/src/support/index.ts deleted file mode 100644 index 3d469a6b6..000000000 --- a/apps/ui-e2e/src/support/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -// *********************************************************** -// This example support/index.js is processed and -// loaded automatically before your test files. -// -// This is a great place to put global configuration and -// behavior that modifies Cypress. -// -// You can change the location of this file or turn off -// automatically serving support files with the -// 'supportFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/configuration -// *********************************************************** - -// Import commands.js using ES2015 syntax: -import './commands'; diff --git a/apps/ui-e2e/tsconfig.e2e.json b/apps/ui-e2e/tsconfig.e2e.json deleted file mode 100644 index f347341bd..000000000 --- a/apps/ui-e2e/tsconfig.e2e.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "sourceMap": false, - "outDir": "../../dist/out-tsc", - "allowJs": true - }, - "include": ["src/**/*.ts", "src/**/*.js"] -} diff --git a/apps/ui-e2e/tsconfig.json b/apps/ui-e2e/tsconfig.json deleted file mode 100644 index 4e87f729f..000000000 --- a/apps/ui-e2e/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "strictNullChecks": true, - "types": ["cypress", "node"] - }, - "include": [], - "files": [], - "references": [ - { - "path": "./tsconfig.e2e.json" - } - ] -} diff --git a/docker-compose.yml b/docker-compose.yml index 184cabd98..97edad7d0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,39 +1,29 @@ version: '3.8' services: jetstream: - build: . + image: jetstream-app init: true depends_on: db: condition: service_healthy + env_file: + - .env.example + - .env environment: NODE_ENV: production ENVIRONMENT: production JETSTREAM_POSTGRES_DBURI: postgres://postgres:postgres@postgres:5432/postgres + EXAMPLE_USER_OVERRIDE: true JETSTREAM_SESSION_SECRET: '${JETSTREAM_SESSION_SECRET}' JETSTREAM_CLIENT_URL: http://localhost:3333/app JETSTREAM_SERVER_DOMAIN: localhost:3333 JETSTREAM_SERVER_URL: http://localhost:3333 - EXAMPLE_USER_OVERRIDE: 'true' - AUTH0_DOMAIN: '${AUTH0_DOMAIN}' - AUTH0_M2M_DOMAIN: '${AUTH0_M2M_DOMAIN}' - AUTH0_CLIENT_ID: '${AUTH0_CLIENT_ID}' - AUTH0_MGMT_CLIENT_ID: '${AUTH0_MGMT_CLIENT_ID}' - AUTH0_MGMT_CLIENT_SECRET: '${AUTH0_MGMT_CLIENT_SECRET}' - AUTH0_CLIENT_SECRET: '${AUTH0_CLIENT_SECRET}' - SFDC_API_VERSION: '${SFDC_API_VERSION}' - SFDC_CONSUMER_SECRET: '${SFDC_CONSUMER_SECRET}' - SFDC_CONSUMER_KEY: '${SFDC_CONSUMER_KEY}' - SFDC_CALLBACK_URL: '${SFDC_CALLBACK_URL}' - NX_ROLLBAR_KEY: '${NX_ROLLBAR_KEY}' - NX_AMPLITUDE_KEY: '${NX_AMPLITUDE_KEY}' - NX_AUTH_AUDIENCE: '${NX_AUTH_AUDIENCE}' ports: - '3333:3333' links: - db db: - image: postgres:14.1-alpine + image: postgres:16.1-alpine restart: always hostname: postgres environment: @@ -53,9 +43,11 @@ services: timeout: 5s retries: 5 db_seed: - build: - context: . - dockerfile: ./Dockerfile.db-migration + image: jetstream-app + command: > + bash -c "yarn db:migrate && yarn db:seed" + links: + - db depends_on: db: condition: service_healthy diff --git a/jest.config.ts b/jest.config.ts index 1dcd5a03f..a7b5d7b01 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -1,3 +1,3 @@ -const { getJestProjects } = require('@nrwl/jest'); +const { getJestProjects } = require('@nx/jest'); export default { projects: getJestProjects() }; diff --git a/jest.preset.js b/jest.preset.js index eab5da72c..b4fceab15 100644 --- a/jest.preset.js +++ b/jest.preset.js @@ -1,4 +1,4 @@ -const nxPreset = require('@nrwl/jest/preset').default; +const nxPreset = require('@nx/jest/preset').default; /** @type {import('jest').Config} */ const config = { @@ -8,7 +8,7 @@ const config = { transform: { '^.+\\.(ts|js|html)$': 'ts-jest', }, - resolver: '@nrwl/jest/plugins/resolver', + resolver: '@nx/jest/plugins/resolver', moduleFileExtensions: ['ts', 'js', 'html'], coverageReporters: ['html'], }; diff --git a/libs/api-config/.babelrc b/libs/api-config/.babelrc index b63f0528f..fd4cbcdef 100644 --- a/libs/api-config/.babelrc +++ b/libs/api-config/.babelrc @@ -1,7 +1,7 @@ { "presets": [ [ - "@nrwl/js/babel", + "@nx/js/babel", { "useBuiltIns": "usage" } diff --git a/libs/api-config/project.json b/libs/api-config/project.json index c25824039..247c2f5a0 100644 --- a/libs/api-config/project.json +++ b/libs/api-config/project.json @@ -5,11 +5,8 @@ "projectType": "library", "targets": { "lint": { - "executor": "@nrwl/linter:eslint", - "outputs": ["{options.outputFile}"], - "options": { - "lintFilePatterns": ["libs/api-config/**/*.ts"] - } + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"] } }, "tags": ["scope:server"] diff --git a/libs/api-config/src/lib/api-db-config.ts b/libs/api-config/src/lib/api-db-config.ts index 1c0b3b41f..0a40dc341 100644 --- a/libs/api-config/src/lib/api-db-config.ts +++ b/libs/api-config/src/lib/api-db-config.ts @@ -15,7 +15,6 @@ if (ENV.PRISMA_DEBUG) { export const prisma = new PrismaClient({ log, - rejectOnNotFound: false, }); export const pgPool = new Pool({ diff --git a/libs/api-config/src/lib/api-rollbar-config.ts b/libs/api-config/src/lib/api-rollbar-config.ts index 89de4ed10..b112c61e6 100644 --- a/libs/api-config/src/lib/api-rollbar-config.ts +++ b/libs/api-config/src/lib/api-rollbar-config.ts @@ -1,4 +1,4 @@ -import * as Rollbar from 'rollbar'; +import Rollbar from 'rollbar'; import { ENV } from './env-config'; export const rollbarServer = new Rollbar({ @@ -9,4 +9,5 @@ export const rollbarServer = new Rollbar({ captureUncaught: true, captureUnhandledRejections: true, enabled: !!ENV.ROLLBAR_SERVER_TOKEN, + nodeSourceMaps: true, }); diff --git a/libs/api-config/src/lib/api-telemetry.ts b/libs/api-config/src/lib/api-telemetry.ts index a2bcaae97..fa3c38e54 100644 --- a/libs/api-config/src/lib/api-telemetry.ts +++ b/libs/api-config/src/lib/api-telemetry.ts @@ -1,15 +1,14 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { credentials, Metadata } from '@grpc/grpc-js'; +import { UserProfileServer } from '@jetstream/types'; +import telemetryApi from '@opentelemetry/api'; import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'; import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc'; import { Resource } from '@opentelemetry/resources'; import { NodeSDK } from '@opentelemetry/sdk-node'; import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; -import * as process from 'process'; import { logger } from './api-logger'; import { ENV } from './env-config'; -import telemetryApi from '@opentelemetry/api'; -import { UserProfileServer } from '@jetstream/types'; // Metadata is passed into to the tracer to provide both the dataset name and the API key required for Honeycomb. // diff --git a/libs/api-config/src/lib/env-config.ts b/libs/api-config/src/lib/env-config.ts index ce8e8f9e0..4a549a848 100644 --- a/libs/api-config/src/lib/env-config.ts +++ b/libs/api-config/src/lib/env-config.ts @@ -1,5 +1,5 @@ import { ensureBoolean, ensureStringValue } from '@jetstream/shared/utils'; -import { UserProfileServer } from '@jetstream/types'; +import { UserProfileServer, UserProfileUi } from '@jetstream/types'; import * as dotenv from 'dotenv'; import { readFileSync } from 'fs-extra'; import { join } from 'path'; @@ -41,11 +41,21 @@ const EXAMPLE_USER: UserProfileServer = { user_id: 'EXAMPLE_USER', }; +const EXAMPLE_USER_PROFILE: UserProfileUi = { + ...EXAMPLE_USER._json, + id: 'EXAMPLE_USER', + userId: 'EXAMPLE_USER', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + preferences: { skipFrontdoorLogin: false }, +}; + export const ENV = { IS_CI: ensureBoolean(process.env.CI), // LOCAL OVERRIDE EXAMPLE_USER_OVERRIDE: ensureBoolean(process.env.EXAMPLE_USER_OVERRIDE), EXAMPLE_USER: process.env.EXAMPLE_USER_OVERRIDE ? EXAMPLE_USER : null, + EXAMPLE_USER_PROFILE: process.env.EXAMPLE_USER_OVERRIDE ? EXAMPLE_USER_PROFILE : null, // SYSTEM NODE_ENV: process.env.NODE_ENV, ENVIRONMENT: process.env.ENVIRONMENT || 'production', @@ -76,7 +86,7 @@ export const ENV = { MAILGUN_PUBLIC_KEY: process.env.MAILGUN_PUBLIC_KEY, MAILGUN_WEBHOOK_KEY: process.env.MAILGUN_WEBHOOK_KEY, // SFDC - SFDC_API_VERSION: process.env.NX_SFDC_API_VERSION || process.env.SFDC_API_VERSION || '57.0', + SFDC_API_VERSION: process.env.NX_SFDC_API_VERSION || process.env.SFDC_API_VERSION || '58.0', SFDC_CONSUMER_SECRET: process.env.SFDC_CONSUMER_SECRET, SFDC_CONSUMER_KEY: process.env.SFDC_CONSUMER_KEY, SFDC_CALLBACK_URL: process.env.SFDC_CALLBACK_URL, diff --git a/libs/api-interfaces/.babelrc b/libs/api-interfaces/.babelrc index fb3fd65a5..f2f380674 100644 --- a/libs/api-interfaces/.babelrc +++ b/libs/api-interfaces/.babelrc @@ -1,5 +1,3 @@ { - "presets": [ - "@nrwl/js/babel" - ] + "presets": ["@nx/js/babel"] } diff --git a/libs/api-interfaces/project.json b/libs/api-interfaces/project.json index e608f9fd5..f0dafd571 100644 --- a/libs/api-interfaces/project.json +++ b/libs/api-interfaces/project.json @@ -6,23 +6,12 @@ "generators": {}, "targets": { "lint": { - "executor": "@nrwl/linter:eslint", - "options": { - "lintFilePatterns": [ - "libs/api-interfaces/**/*.ts", - "libs/api-interfaces/**/*.spec.ts", - "libs/api-interfaces/**/*.spec.tsx", - "libs/api-interfaces/**/*.spec.js", - "libs/api-interfaces/**/*.spec.jsx", - "libs/api-interfaces/**/*.d.ts" - ] - } + "executor": "@nx/eslint:lint" }, "test": { - "executor": "@nrwl/jest:jest", + "executor": "@nx/jest:jest", "options": { - "jestConfig": "libs/api-interfaces/jest.config.ts", - "passWithNoTests": true + "jestConfig": "libs/api-interfaces/jest.config.ts" }, "outputs": ["{workspaceRoot}/coverage/libs/api-interfaces"] } diff --git a/libs/connected/connected-ui/.babelrc b/libs/connected/connected-ui/.babelrc index c00edfd4b..1bfb12338 100644 --- a/libs/connected/connected-ui/.babelrc +++ b/libs/connected/connected-ui/.babelrc @@ -1,4 +1,4 @@ { - "presets": [["@nrwl/react/babel", { "runtime": "automatic", "importSource": "@emotion/react" }]], + "presets": [["@nx/react/babel", { "runtime": "automatic", "importSource": "@emotion/react" }]], "plugins": ["@emotion/babel-plugin"] } diff --git a/libs/connected/connected-ui/.eslintrc.json b/libs/connected/connected-ui/.eslintrc.json index f717563ba..14b8c635e 100644 --- a/libs/connected/connected-ui/.eslintrc.json +++ b/libs/connected/connected-ui/.eslintrc.json @@ -1,5 +1,5 @@ { - "extends": ["plugin:@nrwl/nx/react", "../../../.eslintrc.json"], + "extends": ["plugin:@nx/react", "../../../.eslintrc.json"], "ignorePatterns": ["!**/*"], "overrides": [ { diff --git a/libs/connected/connected-ui/project.json b/libs/connected/connected-ui/project.json index dc897f579..3c70431dc 100644 --- a/libs/connected/connected-ui/project.json +++ b/libs/connected/connected-ui/project.json @@ -5,17 +5,13 @@ "projectType": "library", "targets": { "lint": { - "executor": "@nrwl/linter:eslint", - "options": { - "lintFilePatterns": ["libs/connected/connected-ui/**/*.{ts,tsx,js,jsx}"] - } + "executor": "@nx/eslint:lint" }, "test": { - "executor": "@nrwl/jest:jest", + "executor": "@nx/jest:jest", "outputs": ["{workspaceRoot}/coverage/libs/connected/connected-ui"], "options": { - "jestConfig": "libs/connected/connected-ui/jest.config.ts", - "passWithNoTests": true + "jestConfig": "libs/connected/connected-ui/jest.config.ts" } } }, diff --git a/libs/connected/connected-ui/src/lib/useListMetadata.tsx b/libs/connected/connected-ui/src/lib/useListMetadata.tsx index 80c482f08..c490e8636 100644 --- a/libs/connected/connected-ui/src/lib/useListMetadata.tsx +++ b/libs/connected/connected-ui/src/lib/useListMetadata.tsx @@ -1,5 +1,5 @@ import { logger } from '@jetstream/shared/client-logger'; -import { listMetadata as listMetadataApi } from '@jetstream/shared/data'; +import { listMetadata as listMetadataApi, queryAll } from '@jetstream/shared/data'; import { useRollbar } from '@jetstream/shared/ui-utils'; import { getMapOf, orderObjectsBy, splitArrayToMaxSize } from '@jetstream/shared/utils'; import { ListMetadataResult, MapOf, SalesforceOrgUi } from '@jetstream/types'; @@ -26,6 +26,28 @@ export interface ListMetadataResultItem { items: ListMetadataResult[]; } +interface FolderRecord { + Id: string; + DeveloperName: string; + ParentId: string | null; + Type: + | 'Document' + | 'Email' + | 'Report' + | 'Dashboard' + | 'QuickText' + | 'Macro' + | 'EmailTemplate' + | 'ActionCadence' + | 'AnalyticAssetCollection'; +} + +const getFolderSoqlQuery = (type: string) => { + return `SELECT Id, DeveloperName, ParentId, Type FROM Folder WHERE type = '${type}' ORDER BY Type, ParentId NULLS FIRST`; +}; + +const METADATA_TYPES_WITH_NESTED_FOLDERS = new Set(['Report', 'Dashboard']); + // helper method async function fetchListMetadata( selectedOrg: SalesforceOrgUi, @@ -77,19 +99,59 @@ async function fetchListMetadataForItemsInFolder( skipCacheIfOlderThan ); + /** + * Some metadata types can have nested folders, but metadata returned does not include the full path of the folder + * This generally works to download, but the fullName in package.xml would be incorrect and trying to re-upload the data would fail + * + * To get around this, we query all folders and figure out the full path for each folder and replace the fullName with the full path + * + * @example {'SubSubFolder2': 'RootFolder/SubFolder1/SubSubFolder2'} + */ + const foldersByPath: MapOf = {}; + if (METADATA_TYPES_WITH_NESTED_FOLDERS.has(type)) { + // query all folders and figure out all path combinations + const reportFolders = await queryAll(selectedOrg, getFolderSoqlQuery(type)); + const foldersById = getMapOf(reportFolders.queryResults.records, 'Id'); + + reportFolders.queryResults.records.reduce((foldersByPath, folder) => { + const { DeveloperName, ParentId } = folder; + + if (!ParentId) { + foldersByPath[DeveloperName] = DeveloperName; + } else if (foldersById[ParentId]?.DeveloperName) { + const parentFolder = foldersById[ParentId]; + const parentPath = foldersByPath[parentFolder.DeveloperName]; + foldersByPath[DeveloperName] = `${parentPath}/${DeveloperName}`; + } else { + logger.warn('[ERROR] Could not find parent folder for folder', folder); + } + return foldersByPath; + }, foldersByPath); + } + // we need to fetch for each folder, split into sets of 3 - const folderItems = splitArrayToMaxSize( - data.filter((folder) => folder.manageableState === 'unmanaged'), - MAX_FOLDER_REQUESTS - ); + const folderFullNames = data.filter((folder) => folder.manageableState === 'unmanaged').map(({ fullName }) => fullName); + const folderItems = splitArrayToMaxSize(folderFullNames, MAX_FOLDER_REQUESTS); for (const currFolderItem of folderItems) { if (currFolderItem.length) { - const { data: items, cache } = await listMetadataApi( + const { data: items } = await listMetadataApi( selectedOrg, - currFolderItem.map(({ fullName }) => ({ type, folder: fullName })), + currFolderItem.map((folder) => ({ type, folder })), skipRequestCache ); + + // replace fullName with full path for reports + // this ensues exports properly include the correct filePath + if (METADATA_TYPES_WITH_NESTED_FOLDERS.has(type)) { + items.forEach((item) => { + const [folder, name] = item.fullName.split('/'); + if (folder && name && foldersByPath[folder]) { + item.fullName = `${foldersByPath[folder]}/${name}`; + } + }); + } + outputItems = outputItems.concat(items); } } @@ -102,10 +164,6 @@ async function fetchListMetadataForItemsInFolder( }; } -function defaultFilterFn(item: ListMetadataResult) { - return true; -} - /** * Hook to call ListMetadata * This will make 1 api call per type (or more if folders) @@ -200,7 +258,7 @@ export function useListMetadata(selectedOrg: SalesforceOrgUi) { // tried queue, but hit a stupid error - we may want a queue in the future for parallel requests for (const item of itemsToProcess) { - const { type, inFolder, folder, loading, error, items } = item; + const { type, inFolder } = item; try { let responseItem: ListMetadataResultItem; if (inFolder) { @@ -216,7 +274,6 @@ export function useListMetadata(selectedOrg: SalesforceOrgUi) { setListMetadataItems((previousItems) => ({ ...previousItems, [type]: responseItem })); } catch (ex) { logger.error(ex); - rollbar.error('List Metadata Failed for item', ex); if (!isMounted.current) { break; } @@ -237,7 +294,7 @@ export function useListMetadata(selectedOrg: SalesforceOrgUi) { setInitialLoadFinished(true); } catch (ex) { logger.error(ex); - rollbar.error('List Metadata Failed', ex); + rollbar.error('List Metadata Failed', { message: ex.message, stack: ex.stack }); if (!isMounted.current) { return; } @@ -256,7 +313,7 @@ export function useListMetadata(selectedOrg: SalesforceOrgUi) { */ const loadListMetadataItem = useCallback( async (item: ListMetadataResultItem) => { - const { type } = item; + const { type, inFolder } = item; try { setListMetadataItems((previousItems) => previousItems && previousItems[type] @@ -266,7 +323,16 @@ export function useListMetadata(selectedOrg: SalesforceOrgUi) { } : previousItems ); - const responseItem = await fetchListMetadata(selectedOrg, item, true); + + let responseItem = await fetchListMetadata(selectedOrg, item, true); + + if (inFolder) { + // handle additional fetches required if type is in folder + responseItem = await fetchListMetadataForItemsInFolder(selectedOrg, item, true); + } else { + responseItem = await fetchListMetadata(selectedOrg, item, true); + } + if (!isMounted.current) { return; } diff --git a/libs/connected/connected-ui/src/lib/useListMetadataOld.tsx b/libs/connected/connected-ui/src/lib/useListMetadataOld.tsx deleted file mode 100644 index 8307854f7..000000000 --- a/libs/connected/connected-ui/src/lib/useListMetadataOld.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import { logger } from '@jetstream/shared/client-logger'; -import { clearCacheForOrg, listMetadata as listMetadataApi } from '@jetstream/shared/data'; -import { useNonInitialEffect, useRollbar } from '@jetstream/shared/ui-utils'; -import { orderObjectsBy } from '@jetstream/shared/utils'; -import { ListMetadataResult, SalesforceOrgUi } from '@jetstream/types'; -import formatRelative from 'date-fns/formatRelative'; -import type { ListMetadataQuery } from 'jsforce'; -import { useCallback, useEffect, useRef, useState } from 'react'; - -let _lastRefreshed: string; - -// TODO: we should use this to ensure data types? Or the server could own the transformations? -const defaultTransformFn = (item: ListMetadataResult) => item; - -function decodeFullNameAndApplyTransform(item: ListMetadataResult, additionalTransform: (item: ListMetadataResult) => ListMetadataResult) { - return additionalTransform({ ...item, fullName: decodeURIComponent(item.fullName) }); -} - -export function useListMetadata( - selectedOrg: SalesforceOrgUi, - types: ListMetadataQuery[], - loadOnInit = true, - initialItems?: ListMetadataResult[], - transformItems?: (item: ListMetadataResult) => ListMetadataResult -) { - const isMounted = useRef(true); - const rollbar = useRollbar(); - const [listMetadataItems, setListMetadataItems] = useState(initialItems); - const [hasLoaded, setHasLoaded] = useState(false); - const [loading, setLoading] = useState(true); - const [hasError, setHasError] = useState(false); - const [errorMessage, setErrorMessage] = useState(null); - const [lastRefreshed, setLastRefreshed] = useState(_lastRefreshed); - const [orgIdUsedToFetch, setOrgIdUsedToFetch] = useState(null); - - useEffect(() => { - isMounted.current = true; - return () => { - isMounted.current = false; - }; - }, []); - - useNonInitialEffect(() => { - if (lastRefreshed) { - _lastRefreshed = lastRefreshed; - } - }, [lastRefreshed]); - - useEffect(() => { - if (selectedOrg && hasLoaded && selectedOrg.uniqueId !== orgIdUsedToFetch) { - setHasLoaded(false); - setListMetadataItems(undefined); - } - }, [hasLoaded, orgIdUsedToFetch, selectedOrg]); - - const loadListMetadata = useCallback( - async (clearCache = false) => { - if (!selectedOrg || !types?.length) { - return; - } - const uniqueId = selectedOrg.uniqueId; - if (orgIdUsedToFetch !== uniqueId) { - setOrgIdUsedToFetch(uniqueId); - } - setHasLoaded(true); - try { - setLoading(true); - if (hasError) { - setHasError(false); - setErrorMessage(null); - } - - if (clearCache) { - clearCacheForOrg(selectedOrg); - } - // TODO: create queue here - // only supports 3 types, we should queue using 1 or 2 or 3 types and make as many calls as required - - const resultsWithCache = await listMetadataApi(selectedOrg, types); - if (!isMounted.current || uniqueId !== selectedOrg.uniqueId) { - return; - } - - if (resultsWithCache.cache) { - const cache = resultsWithCache.cache; - setLastRefreshed(`Last updated ${formatRelative(cache.age, new Date())}`); - } - setListMetadataItems( - orderObjectsBy( - resultsWithCache.data.map((item) => decodeFullNameAndApplyTransform(item, transformItems || defaultTransformFn)), - 'fullName' - ) - ); - } catch (ex) { - logger.error(ex); - rollbar.error('List Metadata Failed', ex); - if (!isMounted.current || uniqueId !== selectedOrg.uniqueId) { - return; - } - setHasError(true); - setErrorMessage(ex.message); - } - setLoading(false); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, - [selectedOrg] - ); - - useEffect(() => { - if (loadOnInit && selectedOrg && (!listMetadataItems || !listMetadataItems.length) && !hasLoaded) { - loadListMetadata(); - } - }, [selectedOrg, hasLoaded, loadOnInit, listMetadataItems, loadListMetadata]); - - return { - loadListMetadata, - loading, - hasError, - errorMessage, - listMetadataItems, - lastRefreshed, - }; -} diff --git a/libs/connected/connected-ui/tsconfig.lib.json b/libs/connected/connected-ui/tsconfig.lib.json index 24a19d887..d99ae1b9b 100644 --- a/libs/connected/connected-ui/tsconfig.lib.json +++ b/libs/connected/connected-ui/tsconfig.lib.json @@ -4,7 +4,7 @@ "outDir": "../../../dist/out-tsc", "types": ["node"] }, - "files": ["../../../node_modules/@nrwl/react/typings/cssmodule.d.ts", "../../../node_modules/@nrwl/react/typings/image.d.ts"], + "files": ["../../../node_modules/@nx/react/typings/image.d.ts"], "exclude": ["**/*.spec.ts", "**/*.test.ts", "**/*.spec.tsx", "**/*.test.tsx", "jest.config.ts"], "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] } diff --git a/libs/icon-factory/.babelrc b/libs/icon-factory/.babelrc index 09d67939c..e05c199e3 100644 --- a/libs/icon-factory/.babelrc +++ b/libs/icon-factory/.babelrc @@ -1,4 +1,4 @@ { - "presets": ["@nrwl/react/babel"], + "presets": ["@nx/react/babel"], "plugins": [] } diff --git a/libs/icon-factory/project.json b/libs/icon-factory/project.json index 1f81d32bf..bca8c4cde 100644 --- a/libs/icon-factory/project.json +++ b/libs/icon-factory/project.json @@ -6,15 +6,7 @@ "generators": {}, "targets": { "lint": { - "executor": "@nrwl/linter:eslint", - "options": { - "lintFilePatterns": [ - "libs/icon-factory/**/*.js", - "libs/icon-factory/**/*.jsx", - "libs/icon-factory/**/*.ts", - "libs/icon-factory/**/*.tsx" - ] - } + "executor": "@nx/eslint:lint" } }, "tags": ["scope:shared"] diff --git a/libs/icon-factory/src/lib/icon-factory.tsx b/libs/icon-factory/src/lib/icon-factory.tsx index 38f8aa8bf..684cc91dd 100644 --- a/libs/icon-factory/src/lib/icon-factory.tsx +++ b/libs/icon-factory/src/lib/icon-factory.tsx @@ -15,20 +15,29 @@ import DoctypeIcon_Image from './icons/doctype/Image'; import DoctypeIcon_Pack from './icons/doctype/Pack'; import DoctypeIcon_Xml from './icons/doctype/Xml'; import DoctypeIcon_Zip from './icons/doctype/Zip'; +import StandardIcon_ActionsAndButtons from './icons/standard/ActionsAndButtons'; import StandardIcon_Activations from './icons/standard/Activations'; import StandardIcon_Apex from './icons/standard/Apex'; import StandardIcon_AssetRelationship from './icons/standard/AssetRelationship'; import StandardIcon_BundleConfig from './icons/standard/BundleConfig'; import StandardIcon_DataStreams from './icons/standard/DataStreams'; import StandardIcon_Entity from './icons/standard/Entity'; +import StandardIcon_Events from './icons/standard/Events'; +import StandardIcon_Feed from './icons/standard/Feed'; import StandardIcon_Feedback from './icons/standard/Feedback'; import StandardIcon_Form from './icons/standard/Form'; +import StandardIcon_Formula from './icons/standard/Formula'; import StandardIcon_MultiPicklist from './icons/standard/MultiPicklist'; import StandardIcon_Opportunity from './icons/standard/Opportunity'; +import StandardIcon_Outcome from './icons/standard/Outcome'; import StandardIcon_Portal from './icons/standard/Portal'; +import StandardIcon_PortalRolesAndSubordinates from './icons/standard/PortalRolesAndSubordinates'; import StandardIcon_ProductConsumed from './icons/standard/ProductConsumed'; import StandardIcon_Record from './icons/standard/Record'; +import StandardIcon_RecordCreate from './icons/standard/RecordCreate'; +import StandardIcon_RecordDelete from './icons/standard/RecordDelete'; import StandardIcon_RecordLookup from './icons/standard/RecordLookup'; +import StandardIcon_RecordUpdate from './icons/standard/RecordUpdate'; import StandardIcon_RelatedList from './icons/standard/RelatedList'; import StandardIcon_Settings from './icons/standard/Settings'; import UtilityIcon_Add from './icons/utility/Add'; @@ -69,6 +78,7 @@ import UtilityIcon_Favorite from './icons/utility/Favorite'; import UtilityIcon_File from './icons/utility/File'; import UtilityIcon_Filter from './icons/utility/Filter'; import UtilityIcon_FilterList from './icons/utility/FilterList'; +import UtilityIcon_Formula from './icons/utility/Formula'; import UtilityIcon_Forward from './icons/utility/Forward'; import UtilityIcon_Help from './icons/utility/Help'; import UtilityIcon_HelpDocExt from './icons/utility/HelpDocExt'; @@ -94,7 +104,10 @@ import UtilityIcon_Play from './icons/utility/Play'; import UtilityIcon_Preview from './icons/utility/Preview'; import UtilityIcon_PromptEdit from './icons/utility/PromptEdit'; import UtilityIcon_QuotationMarks from './icons/utility/QuotationMarks'; +import UtilityIcon_RecordCreate from './icons/utility/RecordCreate'; +import UtilityIcon_RecordDelete from './icons/utility/RecordDelete'; import UtilityIcon_RecordLookup from './icons/utility/RecordLookup'; +import UtilityIcon_RecordUpdate from './icons/utility/RecordUpdate'; import UtilityIcon_Refresh from './icons/utility/Refresh'; import UtilityIcon_RemoveFormatting from './icons/utility/RemoveFormatting'; import UtilityIcon_Richtextbulletedlist from './icons/utility/Richtextbulletedlist'; @@ -115,6 +128,7 @@ import UtilityIcon_Strikethrough from './icons/utility/Strikethrough'; import UtilityIcon_Success from './icons/utility/Success'; import UtilityIcon_Switch from './icons/utility/Switch'; import UtilityIcon_Sync from './icons/utility/Sync'; +import UtilityIcon_Table from './icons/utility/Table'; import UtilityIcon_Task from './icons/utility/Task'; import UtilityIcon_Text from './icons/utility/Text'; import UtilityIcon_Underline from './icons/utility/Underline'; @@ -147,21 +161,30 @@ export interface IconObj { } const standardIcons = { + actions_and_buttons: StandardIcon_ActionsAndButtons, activations: StandardIcon_Activations, apex: StandardIcon_Apex, asset_relationship: StandardIcon_AssetRelationship, bundle_config: StandardIcon_BundleConfig, data_streams: StandardIcon_DataStreams, entity: StandardIcon_Entity, + events: StandardIcon_Events, + feed: StandardIcon_Feed, feedback: StandardIcon_Feedback, form: StandardIcon_Form, + formula: StandardIcon_Formula, multi_picklist: StandardIcon_MultiPicklist, opportunity: StandardIcon_Opportunity, + outcome: StandardIcon_Outcome, portal: StandardIcon_Portal, + portal_roles_and_subordinates: StandardIcon_PortalRolesAndSubordinates, product_consumed: StandardIcon_ProductConsumed, + record_create: StandardIcon_RecordCreate, + record_delete: StandardIcon_RecordDelete, + record_lookup: StandardIcon_RecordLookup, + record_update: StandardIcon_RecordUpdate, record: StandardIcon_Record, related_list: StandardIcon_RelatedList, - record_lookup: StandardIcon_RecordLookup, settings: StandardIcon_Settings, } as const; @@ -200,9 +223,9 @@ const utilityIcons = { close: UtilityIcon_Close, collapse_all: UtilityIcon_CollapseAll, component_customization: UtilityIcon_ComponentCustomization, + contract_alt: UtilityIcon_ContractAlt, copy_to_clipboard: UtilityIcon_CopyToClipboard, copy: UtilityIcon_Copy, - contract_alt: UtilityIcon_ContractAlt, dash: UtilityIcon_Dash, date_time: UtilityIcon_DateTime, delete: UtilityIcon_Delete, @@ -219,9 +242,10 @@ const utilityIcons = { file: UtilityIcon_File, filter: UtilityIcon_Filter, filterList: UtilityIcon_FilterList, + formula: UtilityIcon_Formula, forward: UtilityIcon_Forward, - help: UtilityIcon_Help, help_doc_ext: UtilityIcon_HelpDocExt, + help: UtilityIcon_Help, home: UtilityIcon_Home, image: UtilityIcon_Image, info: UtilityIcon_Info, @@ -230,21 +254,24 @@ const utilityIcons = { left: UtilityIcon_Left, link: UtilityIcon_Link, logout: UtilityIcon_Logout, - minimize_window: UtilityIcon_MinimizeWindow, merge_field: UtilityIcon_MergeField, + minimize_window: UtilityIcon_MinimizeWindow, moneybag: UtilityIcon_Moneybag, multi_select_checkbox: UtilityIcon_MultiSelectCheckbox, new_window: UtilityIcon_NewWindow, notification: UtilityIcon_Notification, - open: UtilityIcon_Open, open_folder: UtilityIcon_OpenFolder, + open: UtilityIcon_Open, page: UtilityIcon_Page, paste: UtilityIcon_Paste, play: UtilityIcon_Play, preview: UtilityIcon_Preview, prompt_edit: UtilityIcon_PromptEdit, quotation_marks: UtilityIcon_QuotationMarks, + record_create: UtilityIcon_RecordCreate, + record_delete: UtilityIcon_RecordDelete, record_lookup: UtilityIcon_RecordLookup, + record_update: UtilityIcon_RecordUpdate, refresh: UtilityIcon_Refresh, remove_formatting: UtilityIcon_RemoveFormatting, richtextbulletedlist: UtilityIcon_Richtextbulletedlist, @@ -265,6 +292,7 @@ const utilityIcons = { success: UtilityIcon_Success, switch: UtilityIcon_Switch, sync: UtilityIcon_Sync, + table: UtilityIcon_Table, task: UtilityIcon_Task, text: UtilityIcon_Text, underline: UtilityIcon_Underline, diff --git a/libs/icon-factory/src/lib/icons/action/AddContact.tsx b/libs/icon-factory/src/lib/icons/action/AddContact.tsx index b27bedb24..1ff635f0c 100644 --- a/libs/icon-factory/src/lib/icons/action/AddContact.tsx +++ b/libs/icon-factory/src/lib/icons/action/AddContact.tsx @@ -1,8 +1,7 @@ import * as React from 'react'; - function SvgAddContact(props: React.SVGProps) { return ( -

        - {fieldMetadata?.length && !visibleFieldMetadataRows?.length && ( + {!!fieldMetadata?.length && !visibleFieldMetadataRows?.length && ( )} {visibleFieldMetadataRows && ( @@ -152,13 +167,15 @@ export const UiRecordForm: FunctionComponent = ({
        ))} diff --git a/libs/shared/ui-record-form/src/lib/UiRecordFormField.tsx b/libs/shared/ui-record-form/src/lib/UiRecordFormField.tsx index 219864aff..3b51d3572 100644 --- a/libs/shared/ui-record-form/src/lib/UiRecordFormField.tsx +++ b/libs/shared/ui-record-form/src/lib/UiRecordFormField.tsx @@ -1,12 +1,13 @@ import { css } from '@emotion/react'; import { polyfillFieldDefinition } from '@jetstream/shared/ui-utils'; -import { ListItem, PicklistFieldValueItem } from '@jetstream/types'; +import { ListItem, Maybe, PicklistFieldValueItem, RecordAttributes } from '@jetstream/types'; import { Checkbox, DatePicker, DateTime, Grid, Icon, Input, Picklist, ReadOnlyFormElement, Textarea, TimePicker } from '@jetstream/ui'; import classNames from 'classnames'; import formatISO from 'date-fns/formatISO'; import parseISO from 'date-fns/parseISO'; import roundToNearestMinutes from 'date-fns/roundToNearestMinutes'; import startOfDay from 'date-fns/startOfDay'; +import type { Field } from 'jsforce'; import uniqueId from 'lodash/uniqueId'; import { Fragment, FunctionComponent, ReactNode, SyntheticEvent, useEffect, useState } from 'react'; import { EditableFields } from './ui-record-form-types'; @@ -49,10 +50,14 @@ export interface UiRecordFormFieldProps { disabled?: boolean; initialValue: string | boolean | null; modifiedValue: string | boolean | null; + relatedRecord?: Maybe<{ attributes: RecordAttributes; Name: string }>; showFieldTypes: boolean; omitUndoIndicator?: boolean; + hideLabel?: boolean; + usePortal?: boolean; // picklist values are converted to strings prior to emitting onChange: (field: EditableFields, value: string | boolean | null, isDirty: boolean) => void; + viewRelatedRecord?: (recordId: string, metadata: Field) => void; } function getUndoKey(name: string) { @@ -64,9 +69,13 @@ export const UiRecordFormField: FunctionComponent = ({ disabled, initialValue: _initialValue, modifiedValue, + relatedRecord, showFieldTypes, omitUndoIndicator, + hideLabel = false, + usePortal = false, onChange, + viewRelatedRecord, }) => { const { label, name, labelHelpText, readOnly, metadata } = field; const required = !readOnly && field.required; @@ -227,6 +236,7 @@ export const UiRecordFormField: FunctionComponent = ({ {readOnly && ( = ({ errorMessageId={`${id}-error`} value={(value as string) || ''} bottomBorder + relatedRecord={relatedRecord} + viewRelatedRecord={viewRelatedRecord} /> )} @@ -246,6 +258,7 @@ export const UiRecordFormField: FunctionComponent = ({ = ({ id={id} checked={!!value} label={label} + hideLabel={hideLabel} className="slds-form-element_stacked slds-is-editing" isStandAlone errorMessage={saveError} @@ -293,6 +307,7 @@ export const UiRecordFormField: FunctionComponent = ({ key={key} id={id} label={label} + hideLabel={hideLabel} className="slds-form-element_stacked slds-is-editing" containerDisplay="contents" errorMessage={saveError} @@ -303,6 +318,7 @@ export const UiRecordFormField: FunctionComponent = ({ errorMessageId={`${id}-error`} initialSelectedDate={initialSelectedDate} disabled={disabled} + usePortal={usePortal} onChange={handleDateChange} /> )} @@ -317,15 +333,18 @@ export const UiRecordFormField: FunctionComponent = ({ className: 'slds-form-element_stacked slds-is-editing', containerDisplay: 'contents', errorMessage: saveError, + hideLabel, labelHelp: labelHelpText, helpText: helpText, isRequired: !readOnly && required, hasError: !!saveError, errorMessageId: `${id}-error`, disabled: disabled, + usePortal, }} timeProps={{ label: 'Time', + hideLabel, className: 'slds-form-element_stacked slds-is-editing', stepInMinutes: 1, disabled: disabled, @@ -338,6 +357,7 @@ export const UiRecordFormField: FunctionComponent = ({ = ({