diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..1976190 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,37 @@ +# Python +__pycache__ +*.pyc +**/__pycache__ +**/*.pyc +venv +.venv + +# System-specific files +.DS_Store +**/.DS_Store + +# Docker +docker compose.* +env.d + +# Docs +docs +*.md +*.log + +# Development/test cache & configurations +data +.cache +.circleci +.git +.vscode +.iml +.idea +db.sqlite3 +.mypy_cache +.pylint.d +.pytest_cache + +# Frontend +node_modules +.next diff --git a/.env b/.env new file mode 100644 index 0000000..6c2e673 --- /dev/null +++ b/.env @@ -0,0 +1,14 @@ +DJANGO_CONFIGURATION=Test +DJANGO_SETTINGS_MODULE=conversations.settings +DJANGO_SECRET_KEY=ThisIsAnExampleKeyForTestPurposeOnly +OIDC_OP_JWKS_ENDPOINT=/endpoint-for-test-purpose-only +DB_HOST=localhost +DB_NAME=conversations +DB_USER=dinum +DB_PASSWORD=pass +DB_PORT=15432 + +STORAGES_STATICFILES_BACKEND=django.contrib.staticfiles.storage.StaticFilesStorage +AWS_S3_ENDPOINT_URL=http://localhost:9000 +AWS_S3_ACCESS_KEY_ID=conversations +AWS_S3_SECRET_ACCESS_KEY=password diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1d8785e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,23 @@ +# Set the default behavior for all files +* text=auto eol=lf + +# Binary files (should not be modified) +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.mov binary +*.mp4 binary +*.mp3 binary +*.flv binary +*.fla binary +*.swf binary +*.gz binary +*.zip binary +*.7z binary +*.ttf binary +*.woff binary +*.woff2 binary +*.eot binary +*.pdf binary \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..24e79d0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,6 @@ + diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md new file mode 100644 index 0000000..059dffa --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -0,0 +1,28 @@ +--- +name: ๐Ÿ› Bug Report +about: If something is not working as expected ๐Ÿค”. +labels: ["bug", "triage"] +--- + +## Bug Report + +**Problematic behavior** +A clear and concise description of the behavior. + +**Expected behavior/code** +A clear and concise description of what you expected to happen (or code). + +**Steps to Reproduce** +1. Do this... +2. Then this... +3. And then the bug happens! + +**Environment** +- Docs version: +- Instance url: + +**Possible Solution** + + +**Additional context/Screenshots** +Add any other context about the problem here. If applicable, add screenshots to help explain. diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md new file mode 100644 index 0000000..6793ee6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Feature_request.md @@ -0,0 +1,23 @@ +--- +name: โœจ Feature Request +about: I have a suggestion (and may want to build it ๐Ÿ’ช)! +labels: ["feature", "triage"] +--- + +## Feature Request + +**Is your feature request related to a problem or unsupported use case? Please describe.** +A clear and concise description of what the problem is. For example: I need to do some task and I have an issue... + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. Add any considered drawbacks. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Discovery, Documentation, Adoption, Migration Strategy** +If you can, explain how users will be able to use this and possibly write out some documentation (if applicable). +Maybe add a screenshot or design? + +**Do you want to work on it through a Pull Request?** + diff --git a/.github/ISSUE_TEMPLATE/Support_question.md b/.github/ISSUE_TEMPLATE/Support_question.md new file mode 100644 index 0000000..ede1c10 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Support_question.md @@ -0,0 +1,18 @@ +--- +name: ๐Ÿค— Support Question +about: If you have a question ๐Ÿ’ฌ, or something was not clear from the docs! +labels: ["support", "triage"] +--- +## Support request +**Checks before filing** +Please make sure you have read our [main Readme](https://github.com/suitenumerique/conversations). + +Also make sure it was not already answered in [an open or close issue](https://github.com/suitenumerique/conversations/issues?q=is%3Aissue%20state%3Aopen%20label%3Asupport). + +If your question was not covered, and you feel like it should be, fire away! We'd love to improve our docs! ๐Ÿ‘Œ + +**Topic** +What's the general area of your question: for example, docker setup, database schema, search functionality,... + +**Question** +Try to be as specific as possible so we can help you as best we can. Please be patient ๐Ÿ™ diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..3a6485b --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,22 @@ +## Purpose + +Describe the purpose of this pull request. + + +## Proposal + +- [ ] item 1... +- [ ] item 2... + +## External contributions + +Thank you for your contribution! ๐ŸŽ‰ + +Please ensure the following items are checked before submitting your pull request: +- [ ] I have read and followed the [contributing guidelines](https://github.com/suitenumerique/conversations/blob/main/CONTRIBUTING.md) +- [ ] I have read and agreed to the [Code of Conduct](https://github.com/suitenumerique/conversations/blob/main/CODE_OF_CONDUCT.md) +- [ ] I have signed off my commits with `git commit --signoff` (DCO compliance) +- [ ] I have signed my commits with my SSH or GPG key (`git commit -S`) +- [ ] My commit messages follow the required format: `(type) title description` +- [ ] I have added a changelog entry under `## [Unreleased]` section (if noticeable change) +- [ ] I have added corresponding tests for new features or bug fixes (if applicable) \ No newline at end of file diff --git a/.github/workflows/conversations-frontend.yml b/.github/workflows/conversations-frontend.yml new file mode 100644 index 0000000..3e57af1 --- /dev/null +++ b/.github/workflows/conversations-frontend.yml @@ -0,0 +1,138 @@ +name: Frontend Workflow + +on: + push: + branches: + - main + pull_request: + branches: + - "*" + +jobs: + + install-dependencies: + uses: ./.github/workflows/dependencies.yml + with: + node_version: '22.x' + with-front-dependencies-installation: true + + test-front: + needs: install-dependencies + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "22.x" + + - name: Restore the frontend cache + uses: actions/cache@v4 + with: + path: "src/frontend/**/node_modules" + key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }} + fail-on-cache-miss: true + + - name: Test App + run: cd src/frontend/ && yarn test + + lint-front: + runs-on: ubuntu-latest + needs: install-dependencies + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "22.x" + - name: Restore the frontend cache + uses: actions/cache@v4 + with: + path: "src/frontend/**/node_modules" + key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }} + fail-on-cache-miss: true + + - name: Check linting + run: cd src/frontend/ && yarn lint + + test-e2e-chromium: + runs-on: ubuntu-latest + needs: install-dependencies + timeout-minutes: 20 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "22.x" + + - name: Restore the frontend cache + uses: actions/cache@v4 + with: + path: "src/frontend/**/node_modules" + key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }} + fail-on-cache-miss: true + + - name: Set e2e env variables + run: cat env.d/development/common.e2e.dist >> env.d/development/common.dist + + - name: Install Playwright Browsers + run: cd src/frontend/apps/e2e && yarn install --frozen-lockfile && yarn install-playwright chromium + + - name: Start Docker services + run: make bootstrap-e2e FLUSH_ARGS='--no-input' + + - name: Run e2e tests + run: cd src/frontend/ && yarn e2e:test --project='chromium' + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-chromium-report + path: src/frontend/apps/e2e/report/ + retention-days: 7 + + test-e2e-other-browser: + runs-on: ubuntu-latest + needs: test-e2e-chromium + timeout-minutes: 20 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "22.x" + + - name: Restore the frontend cache + uses: actions/cache@v4 + with: + path: "src/frontend/**/node_modules" + key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }} + fail-on-cache-miss: true + + - name: Set e2e env variables + run: cat env.d/development/common.e2e.dist >> env.d/development/common.dist + + - name: Install Playwright Browsers + run: cd src/frontend/apps/e2e && yarn install --frozen-lockfile && yarn install-playwright firefox webkit chromium + + - name: Start Docker services + run: make bootstrap-e2e FLUSH_ARGS='--no-input' + + - name: Run e2e tests + run: cd src/frontend/ && yarn e2e:test --project=firefox --project=webkit + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-other-report + path: src/frontend/apps/e2e/report/ + retention-days: 7 diff --git a/.github/workflows/conversations.yml b/.github/workflows/conversations.yml new file mode 100644 index 0000000..db36047 --- /dev/null +++ b/.github/workflows/conversations.yml @@ -0,0 +1,204 @@ +name: Main Workflow + +on: + push: + branches: + - main + pull_request: + branches: + - "*" + +jobs: + install-dependencies: + uses: ./.github/workflows/dependencies.yml + with: + with-build_mails: true + + lint-git: + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' # Makes sense only for pull requests + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: show + run: git log + - name: Enforce absence of print statements in code + run: | + ! git diff origin/${{ github.event.pull_request.base.ref }}..HEAD -- . ':(exclude)**/conversations.yml' | grep "print(" + - name: Check absence of fixup commits + run: | + ! git log | grep 'fixup!' + - name: Install gitlint + run: pip install --user requests gitlint + - name: Lint commit messages added to main + run: ~/.local/bin/gitlint --commits origin/${{ github.event.pull_request.base.ref }}..HEAD + + check-changelog: + runs-on: ubuntu-latest + if: | + contains(github.event.pull_request.labels.*.name, 'noChangeLog') == false && + github.event_name == 'pull_request' + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + fetch-depth: 50 + - name: Check that the CHANGELOG has been modified in the current branch + run: git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.event.after }} | grep 'CHANGELOG.md' + + lint-changelog: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Check CHANGELOG max line length + run: | + max_line_length=$(cat CHANGELOG.md | grep -Ev "^\[.*\]: https://github.com" | wc -L) + if [ $max_line_length -ge 80 ]; then + echo "ERROR: CHANGELOG has lines longer than 80 characters." + exit 1 + fi + + lint-spell-mistakes: + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Install codespell + run: pip install --user codespell + - name: Check for typos + run: | + codespell \ + --check-filenames \ + --ignore-words-list "Dokument,afterAll,excpt,statics" \ + --skip "./git/" \ + --skip "**/*.po" \ + --skip "**/*.pot" \ + --skip "**/*.json" \ + --skip "**/yarn.lock" + + lint-back: + runs-on: ubuntu-latest + defaults: + run: + working-directory: src/backend + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Install Python + uses: actions/setup-python@v3 + with: + python-version: "3.13.3" + - name: Upgrade pip and setuptools + run: pip install --upgrade pip setuptools + - name: Install development dependencies + run: pip install --user .[dev] + - name: Check code formatting with ruff + run: ~/.local/bin/ruff format . --diff + - name: Lint code with ruff + run: ~/.local/bin/ruff check . + - name: Lint code with pylint + run: ~/.local/bin/pylint . + + test-back: + runs-on: ubuntu-latest + needs: install-dependencies + + defaults: + run: + working-directory: src/backend + + services: + postgres: + image: postgres:16 + env: + POSTGRES_DB: conversations + POSTGRES_USER: dinum + POSTGRES_PASSWORD: pass + ports: + - 5432:5432 + # needed because the postgres container does not provide a healthcheck + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + + env: + DJANGO_CONFIGURATION: Test + DJANGO_SETTINGS_MODULE: conversations.settings + DJANGO_SECRET_KEY: ThisIsAnExampleKeyForTestPurposeOnly + OIDC_OP_JWKS_ENDPOINT: /endpoint-for-test-purpose-only + DB_HOST: localhost + DB_NAME: conversations + DB_USER: dinum + DB_PASSWORD: pass + DB_PORT: 5432 + STORAGES_STATICFILES_BACKEND: django.contrib.staticfiles.storage.StaticFilesStorage + AWS_S3_ENDPOINT_URL: http://localhost:9000 + AWS_S3_ACCESS_KEY_ID: conversations + AWS_S3_SECRET_ACCESS_KEY: password + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Create writable /data + run: | + sudo mkdir -p /data/media && \ + sudo mkdir -p /data/static + + - name: Restore the mail templates + uses: actions/cache@v4 + id: mail-templates + with: + path: "src/backend/core/templates/mail" + key: mail-templates-${{ hashFiles('src/mail/mjml') }} + fail-on-cache-miss: true + + - name: Start MinIO + run: | + docker pull minio/minio + docker run -d --name minio \ + -p 9000:9000 \ + -e "MINIO_ACCESS_KEY=conversations" \ + -e "MINIO_SECRET_KEY=password" \ + -v /data/media:/data \ + minio/minio server --console-address :9001 /data + + # Tool to wait for a service to be ready + - name: Install Dockerize + run: | + curl -sSL https://github.com/jwilder/dockerize/releases/download/v0.8.0/dockerize-linux-amd64-v0.8.0.tar.gz | sudo tar -C /usr/local/bin -xzv + + - name: Wait for MinIO to be ready + run: | + dockerize -wait tcp://localhost:9000 -timeout 10s + + - name: Configure MinIO + run: | + MINIO=$(docker ps | grep minio/minio | sed -E 's/.*\s+([a-zA-Z0-9_-]+)$/\1/') + docker exec ${MINIO} sh -c \ + "mc alias set conversations http://localhost:9000 conversations password && \ + mc alias ls && \ + mc mb conversations/conversations-media-storage && \ + mc version enable conversations/conversations-media-storage" + + - name: Install Python + uses: actions/setup-python@v3 + with: + python-version: "3.13.3" + + - name: Install development dependencies + run: pip install --user .[dev] + + - name: Install gettext (required to compile messages) and MIME support + run: | + sudo apt-get update + sudo apt-get install -y gettext pandoc shared-mime-info + sudo wget https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types -O /etc/mime.types + + - name: Generate a MO file from strings extracted from the project + run: python manage.py compilemessages + + - name: Run tests + run: ~/.local/bin/pytest -n 2 diff --git a/.github/workflows/crowdin_download.yml b/.github/workflows/crowdin_download.yml new file mode 100644 index 0000000..82d8f6b --- /dev/null +++ b/.github/workflows/crowdin_download.yml @@ -0,0 +1,77 @@ +name: Download translations from Crowdin + +on: + workflow_dispatch: + push: + branches: + - 'release/**' + +jobs: + install-dependencies: + uses: ./.github/workflows/dependencies.yml + with: + node_version: '22.x' + with-front-dependencies-installation: true + + synchronize-with-crowdin: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Create empty source files + run: | + touch src/backend/locale/django.pot + mkdir -p src/frontend/packages/i18n/locales/conversations/ + touch src/frontend/packages/i18n/locales/conversations/translations-crowdin.json + # crowdin workflow + - name: crowdin action + uses: crowdin/github-action@v2 + with: + config: crowdin/config.yml + upload_sources: false + upload_translations: false + download_translations: true + create_pull_request: false + push_translations: false + push_sources: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # A numeric ID, found at https://crowdin.com/project//tools/api + CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} + + # Visit https://crowdin.com/settings#api-key to create this token + CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} + + CROWDIN_BASE_PATH: "../src/" + # frontend i18n + - name: Restore the frontend cache + uses: actions/cache@v4 + with: + path: "src/frontend/**/node_modules" + key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }} + fail-on-cache-miss: true + - name: generate translations files + working-directory: src/frontend + run: yarn i18n:deploy + # Create a new PR + - name: Create a new Pull Request with new translated strings + uses: peter-evans/create-pull-request@v7 + with: + commit-message: | + ๐ŸŒ(i18n) update translated strings + + Update translated files with new translations + title: ๐ŸŒ(i18n) update translated strings + body: | + ## Purpose + + update translated strings + + ## Proposal + + - [x] update translated strings + branch: i18n/update-translations + labels: i18n diff --git a/.github/workflows/crowdin_upload.yml b/.github/workflows/crowdin_upload.yml new file mode 100644 index 0000000..83937c1 --- /dev/null +++ b/.github/workflows/crowdin_upload.yml @@ -0,0 +1,76 @@ +name: Update crowdin sources + +on: + workflow_dispatch: + push: + branches: + - main + +jobs: + install-dependencies: + uses: ./.github/workflows/dependencies.yml + with: + node_version: '22.x' + with-front-dependencies-installation: true + with-build_mails: true + + synchronize-with-crowdin: + needs: install-dependencies + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + # Backend i18n + - name: Install Python + uses: actions/setup-python@v3 + with: + python-version: "3.13.3" + - name: Upgrade pip and setuptools + run: pip install --upgrade pip setuptools + - name: Install development dependencies + run: pip install --user . + working-directory: src/backend + - name: Restore the mail templates + uses: actions/cache@v4 + id: mail-templates + with: + path: "src/backend/core/templates/mail" + key: mail-templates-${{ hashFiles('src/mail/mjml') }} + fail-on-cache-miss: true + - name: Install gettext + run: | + sudo apt-get update + sudo apt-get install -y gettext pandoc + - name: generate pot files + working-directory: src/backend + run: | + DJANGO_CONFIGURATION=Build python manage.py makemessages -a --keep-pot + # frontend i18n + - name: Restore the frontend cache + uses: actions/cache@v4 + with: + path: "src/frontend/**/node_modules" + key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }} + fail-on-cache-miss: true + - name: generate source translation file + working-directory: src/frontend + run: yarn i18n:extract + # crowdin workflow + - name: crowdin action + uses: crowdin/github-action@v2 + with: + config: crowdin/config.yml + upload_sources: true + upload_translations: false + download_translations: false + create_pull_request: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # A numeric ID, found at https://crowdin.com/project//tools/api + CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} + + # Visit https://crowdin.com/settings#api-key to create this token + CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} + + CROWDIN_BASE_PATH: "../src/" diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml new file mode 100644 index 0000000..9d21f81 --- /dev/null +++ b/.github/workflows/dependencies.yml @@ -0,0 +1,85 @@ +name: Dependency reusable workflow + +on: + workflow_call: + inputs: + node_version: + required: false + default: '22.x' + type: string + with-front-dependencies-installation: + type: boolean + default: false + with-build_mails: + type: boolean + default: false + +jobs: + front-dependencies-installation: + if: ${{ inputs.with-front-dependencies-installation == true }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Restore the frontend cache + uses: actions/cache@v4 + id: front-node_modules + with: + path: "src/frontend/**/node_modules" + key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }} + - name: Setup Node.js + if: steps.front-node_modules.outputs.cache-hit != 'true' + uses: actions/setup-node@v4 + with: + node-version: ${{ inputs.node_version }} + - name: Install dependencies + if: steps.front-node_modules.outputs.cache-hit != 'true' + run: cd src/frontend/ && yarn install --frozen-lockfile + - name: Cache install frontend + if: steps.front-node_modules.outputs.cache-hit != 'true' + uses: actions/cache@v4 + with: + path: "src/frontend/**/node_modules" + key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }} + + build-mails: + if: ${{ inputs.with-build_mails == true }} + runs-on: ubuntu-latest + defaults: + run: + working-directory: src/mail + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Restore the mail templates + uses: actions/cache@v4 + id: mail-templates + with: + path: "src/backend/core/templates/mail" + key: mail-templates-${{ hashFiles('src/mail/mjml') }} + + - name: Setup Node.js + if: steps.mail-templates.outputs.cache-hit != 'true' + uses: actions/setup-node@v4 + with: + node-version: ${{ inputs.node_version }} + + - name: Install yarn + if: steps.mail-templates.outputs.cache-hit != 'true' + run: npm install -g yarn + + - name: Install node dependencies + if: steps.mail-templates.outputs.cache-hit != 'true' + run: yarn install --frozen-lockfile + + - name: Build mails + if: steps.mail-templates.outputs.cache-hit != 'true' + run: yarn build + + - name: Cache mail templates + if: steps.mail-templates.outputs.cache-hit != 'true' + uses: actions/cache@v4 + with: + path: "src/backend/core/templates/mail" + key: mail-templates-${{ hashFiles('src/mail/mjml') }} diff --git a/.github/workflows/docker-hub.yml b/.github/workflows/docker-hub.yml new file mode 100644 index 0000000..896582d --- /dev/null +++ b/.github/workflows/docker-hub.yml @@ -0,0 +1,100 @@ +name: Docker Hub Workflow +run-name: Docker Hub Workflow + +on: + workflow_dispatch: + push: + branches: + - 'main' + tags: + - 'v*' + pull_request: + branches: + - 'main' + - 'ci/trivy-fails' + +env: + DOCKER_USER: 1001:127 + +jobs: + build-and-push-backend: + runs-on: ubuntu-latest + steps: + - + name: Checkout repository + uses: actions/checkout@v4 + - + name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: lasuite/conversations-backend + - + name: Login to DockerHub + if: github.event_name != 'pull_request' + run: echo "${{ secrets.DOCKER_HUB_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_HUB_USER }}" --password-stdin + - + name: Run trivy scan + uses: numerique-gouv/action-trivy-cache@main + with: + docker-build-args: '--target backend-production -f Dockerfile' + docker-image-name: 'docker.io/lasuite/conversations-backend:${{ github.sha }}' + - + name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + target: backend-production + build-args: DOCKER_USER=${{ env.DOCKER_USER }}:-1000 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + build-and-push-frontend: + runs-on: ubuntu-latest + steps: + - + name: Checkout repository + uses: actions/checkout@v4 + - + name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: lasuite/conversations-frontend + - + name: Login to DockerHub + if: github.event_name != 'pull_request' + run: echo "${{ secrets.DOCKER_HUB_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_HUB_USER }}" --password-stdin + - + name: Run trivy scan + uses: numerique-gouv/action-trivy-cache@main + with: + docker-build-args: '-f src/frontend/Dockerfile --target frontend-production' + docker-image-name: 'docker.io/lasuite/conversations-frontend:${{ github.sha }}' + - + name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + file: ./src/frontend/Dockerfile + target: frontend-production + build-args: | + DOCKER_USER=${{ env.DOCKER_USER }}:-1000 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + notify-argocd: + needs: + - build-and-push-frontend + - build-and-push-backend + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' + steps: + - uses: numerique-gouv/action-argocd-webhook-notification@main + id: notify + with: + deployment_repo_path: "${{ secrets.DEPLOYMENT_REPO_URL }}" + argocd_webhook_secret: "${{ secrets.ARGOCD_PREPROD_WEBHOOK_SECRET }}" + argocd_url: "${{ vars.ARGOCD_PREPROD_WEBHOOK_URL }}" diff --git a/.github/workflows/helmfile-linter.yaml b/.github/workflows/helmfile-linter.yaml new file mode 100644 index 0000000..6743df9 --- /dev/null +++ b/.github/workflows/helmfile-linter.yaml @@ -0,0 +1,30 @@ +name: Helmfile lint +run-name: Helmfile lint + +on: + push: + pull_request: + branches: + - 'main' + +jobs: + helmfile-lint: + runs-on: ubuntu-latest + container: + image: ghcr.io/helmfile/helmfile:v0.171.0 + steps: + - + name: Checkout repository + uses: actions/checkout@v4 + - + name: Helmfile lint + shell: bash + run: | + set -e + HELMFILE=src/helm/helmfile.yaml + environments=$(awk 'BEGIN {in_env=0} /^environments:/ {in_env=1; next} /^---/ {in_env=0} in_env && /^ [^ ]/ {gsub(/^ /,""); gsub(/:.*$/,""); print}' "$HELMFILE") + for env in $environments; do + echo "################### $env lint ###################" + helmfile -e $env -f $HELMFILE lint || exit 1 + echo -e "\n" + done \ No newline at end of file diff --git a/.github/workflows/release-helm-chart.yaml b/.github/workflows/release-helm-chart.yaml new file mode 100644 index 0000000..2ad3b25 --- /dev/null +++ b/.github/workflows/release-helm-chart.yaml @@ -0,0 +1,34 @@ +name: Release Chart +run-name: Release Chart + +on: + push: + paths: + - src/helm/conversations/** + +jobs: + release: + # depending on default permission settings for your org (contents being read-only or read-write for workloads), you will have to add permissions + # see: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token + permissions: + contents: write + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Cleanup + run: rm -rf ./src/helm/extra + + - name: Install Helm + uses: azure/setup-helm@v4 + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + + - name: Publish Helm charts + uses: numerique-gouv/helm-gh-pages@add-overwrite-option + with: + charts_dir: ./src/helm + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa55738 --- /dev/null +++ b/.gitignore @@ -0,0 +1,78 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST +.DS_Store +.next/ + +# Translations # Translations +*.mo +*.pot + +# Environments +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +env.d/development/* +!env.d/development/*.dist +env.d/terraform + +# npm +node_modules + +# Mails +src/backend/core/templates/mail/ + +# Swagger +**/swagger.json + +# Logs +*.log + +# Terraform +.terraform +*.tfstate +*.tfstate.backup + +# Test & lint +.coverage +.pylint.d +.pytest_cache +db.sqlite3 +.mypy_cache + +# Site media +/data/ + +# IDEs +.idea/ +.vscode/ +*.iml +.devcontainer diff --git a/.gitlint b/.gitlint new file mode 100644 index 0000000..f7373b6 --- /dev/null +++ b/.gitlint @@ -0,0 +1,78 @@ +# All these sections are optional, edit this file as you like. +[general] +# Ignore certain rules, you can reference them by their id or by their full name +# ignore=title-trailing-punctuation, T3 + +# verbosity should be a value between 1 and 3, the commandline -v flags take precedence over this +# verbosity = 2 + +# By default gitlint will ignore merge commits. Set to 'false' to disable. +# ignore-merge-commits=true + +# By default gitlint will ignore fixup commits. Set to 'false' to disable. +# ignore-fixup-commits=true + +# By default gitlint will ignore squash commits. Set to 'false' to disable. +# ignore-squash-commits=true + +# Enable debug mode (prints more output). Disabled by default. +# debug=true + +# Set the extra-path where gitlint will search for user defined rules +# See http://jorisroovers.github.io/gitlint/user_defined_rules for details +extra-path=gitlint/ + +# [title-max-length] +# line-length=80 + +[title-must-not-contain-word] +# Comma-separated list of words that should not occur in the title. Matching is case +# insensitive. It's fine if the keyword occurs as part of a larger word (so "WIPING" +# will not cause a violation, but "WIP: my title" will. +words=wip + +#[title-match-regex] +# python like regex (https://docs.python.org/2/library/re.html) that the +# commit-msg title must be matched to. +# Note that the regex can contradict with other rules if not used correctly +# (e.g. title-must-not-contain-word). +#regex= + +# [B1] +# B1 = body-max-line-length +# line-length=120 +# [body-min-length] +# min-length=5 + +# [body-is-missing] +# Whether to ignore this rule on merge commits (which typically only have a title) +# default = True +# ignore-merge-commits=false + +# [body-changed-file-mention] +# List of files that need to be explicitly mentioned in the body when they are changed +# This is useful for when developers often erroneously edit certain files or git submodules. +# By specifying this rule, developers can only change the file when they explicitly reference +# it in the commit message. +# files=gitlint/rules.py,README.md + +# [author-valid-email] +# python like regex (https://docs.python.org/2/library/re.html) that the +# commit author email address should be matched to +# For example, use the following regex if you only want to allow email addresses from foo.com +# regex=[^@]+@foo.com + +[ignore-by-title] +# Allow empty body & wrong title pattern only when bots (pyup/greenkeeper) +# upgrade dependencies +regex=^(โฌ†๏ธ.*|Update (.*) from (.*) to (.*)|(chore|fix)\(package\): update .*)$ +ignore=B6,UC1 + +# [ignore-by-body] +# Ignore certain rules for commits of which the body has a line that matches a regex +# E.g. Match bodies that have a line that that contain "release" +# regex=(.*)release(.*) +# +# Ignore certain rules, you can reference them by their id or by their full name +# Use 'all' to ignore all rules +# ignore=T1,body-min-length diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2e12393 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,162 @@ +# Django conversations + +# ---- base image to inherit from ---- +FROM python:3.13.3-alpine AS base + +# Upgrade pip to its latest release to speed up dependencies installation +RUN python -m pip install --upgrade pip setuptools + +# Upgrade system packages to install security updates +RUN apk update && \ + apk upgrade + +# ---- Back-end builder image ---- +FROM base AS back-builder + +WORKDIR /builder + +# Install Rust and Cargo using Alpine's package manager +RUN apk add --no-cache \ + build-base \ + libffi-dev \ + rust \ + cargo + +# Copy required python dependencies +COPY ./src/backend /builder + +RUN mkdir /install && \ + pip install --prefix=/install . + + +# ---- mails ---- +FROM node:24 AS mail-builder + +COPY ./src/mail /mail/app + +WORKDIR /mail/app + +RUN yarn install --frozen-lockfile && \ + yarn build + + +# ---- static link collector ---- +FROM base AS link-collector +ARG CONVERSATIONS_STATIC_ROOT=/data/static + +# Install pango & rdfind +RUN apk add \ + pango \ + rdfind + +# Copy installed python dependencies +COPY --from=back-builder /install /usr/local + +# Copy conversations application (see .dockerignore) +COPY ./src/backend /app/ + +WORKDIR /app + +# collectstatic +RUN DJANGO_CONFIGURATION=Build \ + python manage.py collectstatic --noinput + +# Replace duplicated file by a symlink to decrease the overall size of the +# final image +RUN rdfind -makesymlinks true -followsymlinks true -makeresultsfile false ${CONVERSATIONS_STATIC_ROOT} + +# ---- Core application image ---- +FROM base AS core + +ENV PYTHONUNBUFFERED=1 + +# Install required system libs +RUN apk add \ + cairo \ + file \ + font-noto \ + font-noto-emoji \ + gettext \ + gdk-pixbuf \ + libffi-dev \ + pango \ + shared-mime-info + +RUN wget https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types -O /etc/mime.types + +# Copy entrypoint +COPY ./docker/files/usr/local/bin/entrypoint /usr/local/bin/entrypoint + +# Give the "root" group the same permissions as the "root" user on /etc/passwd +# to allow a user belonging to the root group to add new users; typically the +# docker user (see entrypoint). +RUN chmod g=u /etc/passwd + +# Copy installed python dependencies +COPY --from=back-builder /install /usr/local + +# Copy conversations application (see .dockerignore) +COPY ./src/backend /app/ + +WORKDIR /app + +# Generate compiled translation messages +RUN DJANGO_CONFIGURATION=Build \ + python manage.py compilemessages + + +# We wrap commands run in this container by the following entrypoint that +# creates a user on-the-fly with the container user ID (see USER) and root group +# ID. +ENTRYPOINT [ "/usr/local/bin/entrypoint" ] + +# ---- Development image ---- +FROM core AS backend-development + +# Switch back to the root user to install development dependencies +USER root:root + +# Install psql +RUN apk add postgresql-client + +# Uninstall conversations and re-install it in editable mode along with development +# dependencies +RUN pip uninstall -y conversations +RUN pip install -e .[dev] + +# Restore the un-privileged user running the application +ARG DOCKER_USER +USER ${DOCKER_USER} + +# Target database host (e.g. database engine following docker compose services +# name) & port +ENV DB_HOST=postgresql \ + DB_PORT=5432 + +# Run django development server +CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] + +# ---- Production image ---- +FROM core AS backend-production + +# Remove apk cache, we don't need it anymore +RUN rm -rf /var/cache/apk/* + +ARG CONVERSATIONS_STATIC_ROOT=/data/static + +# Gunicorn +RUN mkdir -p /usr/local/etc/gunicorn +COPY docker/files/usr/local/etc/gunicorn/conversations.py /usr/local/etc/gunicorn/conversations.py + +# Un-privileged user running the application +ARG DOCKER_USER +USER ${DOCKER_USER} + +# Copy statics +COPY --from=link-collector ${CONVERSATIONS_STATIC_ROOT} ${CONVERSATIONS_STATIC_ROOT} + +# Copy conversations mails +COPY --from=mail-builder /mail/backend/core/templates/mail /app/core/templates/mail + +# The default command runs gunicorn WSGI server in conversations's main module +CMD ["gunicorn", "-c", "/usr/local/etc/gunicorn/conversations.py", "conversations.wsgi:application"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ce6f9cf --- /dev/null +++ b/Makefile @@ -0,0 +1,375 @@ +# /!\ /!\ /!\ /!\ /!\ /!\ /!\ DISCLAIMER /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ +# +# This Makefile is only meant to be used for DEVELOPMENT purpose as we are +# changing the user id that will run in the container. +# +# PLEASE DO NOT USE IT FOR YOUR CI/PRODUCTION/WHATEVER... +# +# /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ +# +# Note to developers: +# +# While editing this file, please respect the following statements: +# +# 1. Every variable should be defined in the ad hoc VARIABLES section with a +# relevant subsection +# 2. Every new rule should be defined in the ad hoc RULES section with a +# relevant subsection depending on the targeted service +# 3. Rules should be sorted alphabetically within their section +# 4. When a rule has multiple dependencies, you should: +# - duplicate the rule name to add the help string (if required) +# - write one dependency per line to increase readability and diffs +# 5. .PHONY rule statement should be written after the corresponding rule +# ============================================================================== +# VARIABLES + +BOLD := \033[1m +RESET := \033[0m +GREEN := \033[1;32m + + +# -- Database + +DB_HOST = postgresql +DB_PORT = 5432 + +# -- Docker +# Get the current user ID to use for docker run and docker exec commands +DOCKER_UID = $(shell id -u) +DOCKER_GID = $(shell id -g) +DOCKER_USER = $(DOCKER_UID):$(DOCKER_GID) +COMPOSE = DOCKER_USER=$(DOCKER_USER) docker compose +COMPOSE_E2E = DOCKER_USER=$(DOCKER_USER) docker compose -f compose.yml -f compose-e2e.yml +COMPOSE_EXEC = $(COMPOSE) exec +COMPOSE_EXEC_APP = $(COMPOSE_EXEC) app-dev +COMPOSE_RUN = $(COMPOSE) run --rm +COMPOSE_RUN_APP = $(COMPOSE_RUN) app-dev +COMPOSE_RUN_CROWDIN = $(COMPOSE_RUN) crowdin crowdin + +# -- Backend +MANAGE = $(COMPOSE_RUN_APP) python manage.py +MAIL_YARN = $(COMPOSE_RUN) -w /app/src/mail node yarn + +# -- Frontend +PATH_FRONT = ./src/frontend +PATH_FRONT_CONVERSATIONS = $(PATH_FRONT)/apps/conversations + +# ============================================================================== +# RULES + +default: help + +data/media: + @mkdir -p data/media + +data/static: + @mkdir -p data/static + +# -- Project + +create-env-files: ## Copy the dist env files to env files +create-env-files: \ + env.d/development/common \ + env.d/development/crowdin \ + env.d/development/postgresql \ + env.d/development/kc_postgresql +.PHONY: create-env-files + +pre-bootstrap: \ + data/media \ + data/static \ + create-env-files +.PHONY: pre-bootstrap + +post-bootstrap: \ + migrate \ + demo \ + back-i18n-compile \ + mails-install \ + mails-build +.PHONY: post-bootstrap + + +bootstrap: ## Prepare Docker developmentimages for the project +bootstrap: \ + pre-bootstrap \ + build \ + post-bootstrap \ + run +.PHONY: bootstrap + +bootstrap-e2e: ## Prepare Docker production images to be used for e2e tests +bootstrap-e2e: \ + pre-bootstrap \ + build-e2e \ + post-bootstrap \ + run-e2e +.PHONY: bootstrap-e2e + +# -- Docker/compose +build: cache ?= +build: ## build the project containers + @$(MAKE) build-backend cache=$(cache) + @$(MAKE) build-frontend cache=$(cache) +.PHONY: build + +build-backend: cache ?= +build-backend: ## build the app-dev container + @$(COMPOSE) build app-dev $(cache) +.PHONY: build-backend + +build-frontend: cache ?= +build-frontend: ## build the frontend container + @$(COMPOSE) build frontend-development $(cache) +.PHONY: build-frontend + +build-e2e: cache ?= +build-e2e: ## build the e2e container + @$(MAKE) build-backend cache=$(cache) + @$(COMPOSE_E2E) build frontend $(cache) +.PHONY: build-e2e + +down: ## stop and remove containers, networks, images, and volumes + @$(COMPOSE_E2E) down +.PHONY: down + +logs: ## display app-dev logs (follow mode) + @$(COMPOSE) logs -f app-dev +.PHONY: logs + +run-backend: ## Start only the backend application and all needed services + @$(COMPOSE) up --force-recreate -d nginx ml-flow app-dev +.PHONY: run-backend + +run: ## start the wsgi (production) and development server +run: + @$(MAKE) run-backend + @$(COMPOSE) up --force-recreate -d frontend-development +.PHONY: run + +run-e2e: ## start the e2e server +run-e2e: + @$(MAKE) run-backend + @$(COMPOSE_E2E) up --force-recreate -d frontend +.PHONY: run-e2e + +status: ## an alias for "docker compose ps" + @$(COMPOSE_E2E) ps +.PHONY: status + +stop: ## stop the development server using Docker + @$(COMPOSE_E2E) stop +.PHONY: stop + +# -- Backend + +demo: ## flush db then create a demo for load testing purpose + @$(MAKE) resetdb + @$(MANAGE) create_demo +.PHONY: demo + +# Nota bene: Black should come after isort just in case they don't agree... +lint: ## lint back-end python sources +lint: \ + lint-ruff-format \ + lint-ruff-check \ + lint-pylint +.PHONY: lint + +lint-ruff-format: ## format back-end python sources with ruff + @echo 'lint:ruff-format startedโ€ฆ' + @$(COMPOSE_RUN_APP) ruff format . +.PHONY: lint-ruff-format + +lint-ruff-check: ## lint back-end python sources with ruff + @echo 'lint:ruff-check startedโ€ฆ' + @$(COMPOSE_RUN_APP) ruff check . --fix +.PHONY: lint-ruff-check + +lint-pylint: ## lint back-end python sources with pylint only on changed files from main + @echo 'lint:pylint startedโ€ฆ' + bin/pylint --diff-only=origin/main +.PHONY: lint-pylint + +test: ## run project tests + @$(MAKE) test-back-parallel +.PHONY: test + +test-back: ## run back-end tests + @args="$(filter-out $@,$(MAKECMDGOALS))" && \ + bin/pytest $${args:-${1}} +.PHONY: test-back + +test-back-parallel: ## run all back-end tests in parallel + @args="$(filter-out $@,$(MAKECMDGOALS))" && \ + bin/pytest -n auto $${args:-${1}} +.PHONY: test-back-parallel + +makemigrations: ## run django makemigrations for the conversations project. + @echo "$(BOLD)Running makemigrations$(RESET)" + @$(COMPOSE) up -d postgresql + @$(MANAGE) makemigrations +.PHONY: makemigrations + +migrate: ## run django migrations for the conversations project. + @echo "$(BOLD)Running migrations$(RESET)" + @$(COMPOSE) up -d postgresql + @$(MANAGE) migrate +.PHONY: migrate + +superuser: ## Create an admin superuser with password "admin" + @echo "$(BOLD)Creating a Django superuser$(RESET)" + @$(MANAGE) createsuperuser --email admin@example.com --password admin +.PHONY: superuser + +back-i18n-compile: ## compile the gettext files + @$(MANAGE) compilemessages --ignore="venv/**/*" +.PHONY: back-i18n-compile + +back-i18n-generate: ## create the .pot files used for i18n + @$(MANAGE) makemessages -a --keep-pot --all +.PHONY: back-i18n-generate + +shell: ## connect to database shell + @$(MANAGE) shell #_plus +.PHONY: dbshell + +# -- Database + +dbshell: ## connect to database shell + docker compose exec app-dev python manage.py dbshell +.PHONY: dbshell + +resetdb: FLUSH_ARGS ?= +resetdb: ## flush database and create a superuser "admin" + @echo "$(BOLD)Flush database$(RESET)" + @$(MANAGE) flush $(FLUSH_ARGS) + @${MAKE} superuser +.PHONY: resetdb + +env.d/development/common: + cp -n env.d/development/common.dist env.d/development/common + +env.d/development/postgresql: + cp -n env.d/development/postgresql.dist env.d/development/postgresql + +env.d/development/kc_postgresql: + cp -n env.d/development/kc_postgresql.dist env.d/development/kc_postgresql + +# -- Internationalization + +env.d/development/crowdin: + cp -n env.d/development/crowdin.dist env.d/development/crowdin + +crowdin-download: ## Download translated message from crowdin + @$(COMPOSE_RUN_CROWDIN) download -c crowdin/config.yml +.PHONY: crowdin-download + +crowdin-download-sources: ## Download sources from Crowdin + @$(COMPOSE_RUN_CROWDIN) download sources -c crowdin/config.yml +.PHONY: crowdin-download-sources + +crowdin-upload: ## Upload source translations to crowdin + @$(COMPOSE_RUN_CROWDIN) upload sources -c crowdin/config.yml +.PHONY: crowdin-upload + +i18n-compile: ## compile all translations +i18n-compile: \ + back-i18n-compile \ + frontend-i18n-compile +.PHONY: i18n-compile + +i18n-generate: ## create the .pot files and extract frontend messages +i18n-generate: \ + back-i18n-generate \ + frontend-i18n-generate +.PHONY: i18n-generate + +i18n-download-and-compile: ## download all translated messages and compile them to be used by all applications +i18n-download-and-compile: \ + crowdin-download \ + i18n-compile +.PHONY: i18n-download-and-compile + +i18n-generate-and-upload: ## generate source translations for all applications and upload them to Crowdin +i18n-generate-and-upload: \ + i18n-generate \ + crowdin-upload +.PHONY: i18n-generate-and-upload + + +# -- Mail generator + +mails-build: ## Convert mjml files to html and text + @$(MAIL_YARN) build +.PHONY: mails-build + +mails-build-html-to-plain-text: ## Convert html files to text + @$(MAIL_YARN) build-html-to-plain-text +.PHONY: mails-build-html-to-plain-text + +mails-build-mjml-to-html: ## Convert mjml files to html and text + @$(MAIL_YARN) build-mjml-to-html +.PHONY: mails-build-mjml-to-html + +mails-install: ## install the mail generator + @$(MAIL_YARN) install +.PHONY: mails-install + +# -- Misc +clean: ## restore repository state as it was freshly cloned + git clean -idx +.PHONY: clean + +help: + @echo "$(BOLD)conversations Makefile" + @echo "Please use 'make $(BOLD)target$(RESET)' where $(BOLD)target$(RESET) is one of:" + @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(firstword $(MAKEFILE_LIST)) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "$(GREEN)%-30s$(RESET) %s\n", $$1, $$2}' +.PHONY: help + +# Front +frontend-development-install: ## install the frontend locally + cd $(PATH_FRONT_CONVERSATIONS) && yarn +.PHONY: frontend-development-install + +frontend-lint: ## run the frontend linter + cd $(PATH_FRONT) && yarn lint +.PHONY: frontend-lint + +run-frontend-development: ## Run the frontend in development mode + #@$(COMPOSE) stop frontend frontend-development + cd $(PATH_FRONT_CONVERSATIONS) && yarn dev +.PHONY: run-frontend-development + +frontend-i18n-extract: ## Extract the frontend translation inside a json to be used for crowdin + cd $(PATH_FRONT) && yarn i18n:extract +.PHONY: frontend-i18n-extract + +frontend-i18n-generate: ## Generate the frontend json files used for crowdin +frontend-i18n-generate: \ + crowdin-download-sources \ + frontend-i18n-extract +.PHONY: frontend-i18n-generate + +frontend-i18n-compile: ## Format the crowin json files used deploy to the apps + cd $(PATH_FRONT) && yarn i18n:deploy +.PHONY: frontend-i18n-compile + +# -- K8S +build-k8s-cluster: ## build the kubernetes cluster using kind + ./bin/start-kind.sh +.PHONY: build-k8s-cluster + +start-tilt: ## start the kubernetes cluster using kind + tilt up -f ./bin/Tiltfile +.PHONY: build-k8s-cluster + +bump-packages-version: VERSION_TYPE ?= minor +bump-packages-version: ## bump the version of the project - VERSION_TYPE can be "major", "minor", "patch" + cd ./src/mail && yarn version --no-git-tag-version --$(VERSION_TYPE) + cd ./src/frontend/ && yarn version --no-git-tag-version --$(VERSION_TYPE) + cd ./src/frontend/apps/e2e/ && yarn version --no-git-tag-version --$(VERSION_TYPE) + cd ./src/frontend/apps/conversations/ && yarn version --no-git-tag-version --$(VERSION_TYPE) + cd ./src/frontend/packages/eslint-config-conversations/ && yarn version --no-git-tag-version --$(VERSION_TYPE) + cd ./src/frontend/packages/i18n/ && yarn version --no-git-tag-version --$(VERSION_TYPE) +.PHONY: bump-packages-version diff --git a/bin/Tiltfile b/bin/Tiltfile new file mode 100644 index 0000000..c6d7459 --- /dev/null +++ b/bin/Tiltfile @@ -0,0 +1,60 @@ +load('ext://uibutton', 'cmd_button', 'bool_input', 'location') +load('ext://namespace', 'namespace_create', 'namespace_inject') +namespace_create('conversations') + +docker_build( + 'localhost:5001/conversations-backend:latest', + context='..', + dockerfile='../Dockerfile', + only=['./src/backend', './src/mail', './docker'], + target = 'backend-production', + live_update=[ + sync('../src/backend', '/app'), + run( + 'pip install -r /app/requirements.txt', + trigger=['./api/requirements.txt'] + ) + ] +) + +docker_build( + 'localhost:5001/conversations-frontend:latest', + context='..', + dockerfile='../src/frontend/Dockerfile', + only=['./src/frontend', './docker', './.dockerignore'], + target = 'conversations', + live_update=[ + sync('../src/frontend', '/home/frontend'), + ] +) + +k8s_resource('conversations-conversations-backend-migrate', resource_deps=['postgres-postgresql']) +k8s_resource('conversations-conversations-backend-createsuperuser', resource_deps=['conversations-conversations-backend-migrate']) +k8s_resource('conversations-conversations-backend', resource_deps=['conversations-conversations-backend-migrate']) +k8s_yaml(local('cd ../src/helm && helmfile -n conversations -e dev template .')) + +migration = ''' +set -eu +# get k8s pod name from tilt resource name +POD_NAME="$(tilt get kubernetesdiscovery conversations-backend -ojsonpath='{.status.pods[0].name}')" +kubectl -n conversations exec "$POD_NAME" -- python manage.py makemigrations +''' +cmd_button('Make migration', + argv=['sh', '-c', migration], + resource='conversations-backend', + icon_name='developer_board', + text='Run makemigration', +) + +pod_migrate = ''' +set -eu +# get k8s pod name from tilt resource name +POD_NAME="$(tilt get kubernetesdiscovery conversations-backend -ojsonpath='{.status.pods[0].name}')" +kubectl -n conversations exec "$POD_NAME" -- python manage.py migrate --no-input +''' +cmd_button('Migrate db', + argv=['sh', '-c', pod_migrate], + resource='conversations-backend', + icon_name='developer_board', + text='Run database migration', +) diff --git a/bin/_config.sh b/bin/_config.sh new file mode 100644 index 0000000..7df0684 --- /dev/null +++ b/bin/_config.sh @@ -0,0 +1,155 @@ +#!/usr/bin/env bash + +set -eo pipefail + +REPO_DIR="$(cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd)" +UNSET_USER=0 + +TERRAFORM_DIRECTORY="./env.d/terraform" +COMPOSE_FILE="${REPO_DIR}/compose.yml" + + +# _set_user: set (or unset) default user id used to run docker commands +# +# usage: _set_user +# +# You can override default user ID (the current host user ID), by defining the +# USER_ID environment variable. +# +# To avoid running docker commands with a custom user, please set the +# $UNSET_USER environment variable to 1. +function _set_user() { + + if [ $UNSET_USER -eq 1 ]; then + USER_ID="" + return + fi + + # USER_ID = USER_ID or `id -u` if USER_ID is not set + USER_ID=${USER_ID:-$(id -u)} + + echo "๐Ÿ™‹(user) ID: ${USER_ID}" +} + +# docker_compose: wrap docker compose command +# +# usage: docker_compose [options] [ARGS...] +# +# options: docker compose command options +# ARGS : docker compose command arguments +function _docker_compose() { + + echo "๐Ÿณ(compose) file: '${COMPOSE_FILE}'" + docker compose \ + -f "${COMPOSE_FILE}" \ + --project-directory "${REPO_DIR}" \ + "$@" +} + +# _dc_run: wrap docker compose run command +# +# usage: _dc_run [options] [ARGS...] +# +# options: docker compose run command options +# ARGS : docker compose run command arguments +function _dc_run() { + _set_user + + user_args="--user=$USER_ID" + if [ -z $USER_ID ]; then + user_args="" + fi + + _docker_compose run --rm $user_args "$@" +} + +# _dc_exec: wrap docker compose exec command +# +# usage: _dc_exec [options] [ARGS...] +# +# options: docker compose exec command options +# ARGS : docker compose exec command arguments +function _dc_exec() { + _set_user + + echo "๐Ÿณ(compose) exec command: '\$@'" + + user_args="--user=$USER_ID" + if [ -z $USER_ID ]; then + user_args="" + fi + + _docker_compose exec $user_args "$@" +} + +# _django_manage: wrap django's manage.py command with docker compose +# +# usage : _django_manage [ARGS...] +# +# ARGS : django's manage.py command arguments +function _django_manage() { + _dc_run "app-dev" python manage.py "$@" +} + +# _set_openstack_project: select an OpenStack project from the openrc files defined in the +# terraform directory. +# +# usage: _set_openstack_project +# +# If necessary the script will prompt the user to choose a project from those available +function _set_openstack_project() { + + declare prompt + declare -a projects + declare -i default=1 + declare -i choice=0 + declare -i n_projects + + # List projects by looking in the "./env.d/terraform" directory + # and store them in an array + read -r -a projects <<< "$( + find "${TERRAFORM_DIRECTORY}" -maxdepth 1 -mindepth 1 -type d | + sed 's|'"${TERRAFORM_DIRECTORY}\/"'||' | + xargs + )" + nb_projects=${#projects[@]} + + if [[ ${nb_projects} -le 0 ]]; then + echo "There are no OpenStack projects defined..." >&2 + echo "To add one, create a subdirectory in \"${TERRAFORM_DIRECTORY}\" with the name" \ + "of your project and copy your \"openrc.sh\" file into it." >&2 + exit 10 + fi + + if [[ ${nb_projects} -gt 1 ]]; then + prompt="Select an OpenStack project to target:\\n" + for (( i=0; i&2 echo "Invalid choice ${choice} (should be <= ${nb_projects})") + exit 11 + fi + + if [[ ${choice} -le 0 ]]; then + choice=${default} + fi + fi + + project=${projects[$((choice-1))]} + # Check that the openrc.sh file exists for this project + if [ ! -f "${TERRAFORM_DIRECTORY}/${project}/openrc.sh" ]; then + (>&2 echo "Missing \"openrc.sh\" file in \"${TERRAFORM_DIRECTORY}/${project}\". Check documentation.") + exit 12 + fi + + echo "${project}" +} diff --git a/bin/compose b/bin/compose new file mode 100755 index 0000000..1adb3d8 --- /dev/null +++ b/bin/compose @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +# shellcheck source=bin/_config.sh +source "$(dirname "${BASH_SOURCE[0]}")/_config.sh" + +_docker_compose "$@" diff --git a/bin/install-hooks.sh b/bin/install-hooks.sh new file mode 100644 index 0000000..7d1c790 --- /dev/null +++ b/bin/install-hooks.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +mkdir -p "$(dirname -- "${BASH_SOURCE[0]}")/../.git/hooks/" +PRE_COMMIT_FILE="$(dirname -- "${BASH_SOURCE[0]}")/../.git/hooks/pre-commit" + +cat <<'EOF' >$PRE_COMMIT_FILE +#!/bin/bash + +# directories containing potential secrets +DIRS="." + +bold=$(tput bold) +normal=$(tput sgr0) + +# allow to read user input, assigns stdin to keyboard +exec + sh -c " + /usr/bin/mc alias set conversations http://minio:9000 conversations password && \ + /usr/bin/mc mb conversations/conversations-media-storage && \ + /usr/bin/mc version enable conversations/conversations-media-storage && \ + exit 0;" + + app-dev: + build: + context: . + target: backend-development + args: + DOCKER_USER: ${DOCKER_USER:-1000} + user: ${DOCKER_USER:-1000} + image: conversations:backend-development + environment: + - PYLINTHOME=/app/.pylint.d + - DJANGO_CONFIGURATION=Development + env_file: + - env.d/development/common + - env.d/development/postgresql + extra_hosts: + - "host.docker.internal:host-gateway" + ports: + - "8071:8000" + volumes: + - ./src/backend:/app + - ./data/static:/data/static + depends_on: + postgresql: + condition: service_healthy + restart: true + maildev: + condition: service_started + redis: + condition: service_started + createbuckets: + condition: service_started + ai_runner: + condition: service_started + + nginx: + image: nginx:1.25 + ports: + - "8083:8083" + volumes: + - ./docker/files/etc/nginx/conf.d:/etc/nginx/conf.d:ro + depends_on: + app-dev: + condition: service_started + keycloak: + condition: service_healthy + restart: true + + frontend-development: + user: "${DOCKER_USER:-1000}" + build: + context: . + dockerfile: ./src/frontend/Dockerfile + target: conversations-dev + args: + API_ORIGIN: "http://localhost:8071" + image: conversations:frontend-development + volumes: + - ./src/frontend:/home/frontend + - /home/frontend/node_modules + - /home/frontend/apps/conversations/node_modules + ports: + - "3000:3000" + + crowdin: + image: crowdin/cli:3.16.0 + volumes: + - ".:/app" + env_file: + - env.d/development/crowdin + user: "${DOCKER_USER:-1000}" + working_dir: /app + + node: + image: node:22 + user: "${DOCKER_USER:-1000}" + environment: + HOME: /tmp + volumes: + - ".:/app" + + kc_postgresql: + image: postgres:14.3 + healthcheck: + test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"] + interval: 1s + timeout: 2s + retries: 300 + ports: + - "5433:5432" + env_file: + - env.d/development/kc_postgresql + + keycloak: + image: quay.io/keycloak/keycloak:20.0.1 + volumes: + - ./docker/auth/realm.json:/opt/keycloak/data/import/realm.json + command: + - start-dev + - --features=preview + - --import-realm + - --proxy=edge + - --hostname-url=http://localhost:8083 + - --hostname-admin-url=http://localhost:8083/ + - --hostname-strict=false + - --hostname-strict-https=false + - --health-enabled=true + - --metrics-enabled=true + healthcheck: + test: ["CMD", "curl", "--head", "-fsS", "http://localhost:8080/health/ready"] + interval: 1s + timeout: 2s + retries: 300 + environment: + KEYCLOAK_ADMIN: admin + KEYCLOAK_ADMIN_PASSWORD: admin + KC_DB: postgres + KC_DB_URL_HOST: kc_postgresql + KC_DB_URL_DATABASE: keycloak + KC_DB_PASSWORD: pass + KC_DB_USERNAME: conversations + KC_DB_SCHEMA: public + PROXY_ADDRESS_FORWARDING: 'true' + ports: + - "8080:8080" + depends_on: + kc_postgresql: + condition: service_healthy + restart: true + + ml-flow: + image: ghcr.io/mlflow/mlflow:v3.0.0 + environment: + MLFLOW_TRACKING_URI: http://localhost:5050 + MLFLOW_ARTIFACT_ROOT: /mlflow/artifacts + ports: + - "5050:5050" + volumes: + - ./data/mlflow:/mlflow/artifacts + command: mlflow server --host 0.0.0.0:5050 + + ai_runner: + provider: + type: model + options: + model: ai/smollm2 \ No newline at end of file diff --git a/crowdin/config.yml b/crowdin/config.yml new file mode 100644 index 0000000..2db5cf7 --- /dev/null +++ b/crowdin/config.yml @@ -0,0 +1,29 @@ +# +# Your crowdin's credentials +# +api_token_env: CROWDIN_PERSONAL_TOKEN +project_id_env: CROWDIN_PROJECT_ID +base_path_env: CROWDIN_BASE_PATH + +# +# Choose file structure in crowdin +# e.g. true or false +# +preserve_hierarchy: true + +# +# Files configuration +# +files: [ + { + source : "/backend/locale/django.pot", + dest: "/backend-conversations.pot", + translation : "/backend/locale/%locale_with_underscore%/LC_MESSAGES/django.po" + }, + { + source: "/frontend/packages/i18n/locales/conversations/translations-crowdin.json", + dest: "/frontend-conversations.json", + translation: "/frontend/packages/i18n/locales/conversations/%two_letters_code%/translations.json", + skip_untranslated_strings: true, + }, +] diff --git a/docker/auth/realm.json b/docker/auth/realm.json new file mode 100644 index 0000000..29a205e --- /dev/null +++ b/docker/auth/realm.json @@ -0,0 +1,2281 @@ +{ + "id": "ccf4fd40-4286-474d-854a-4714282a8bec", + "realm": "conversations", + "notBefore": 0, + "defaultSignatureAlgorithm": "RS256", + "revokeRefreshToken": false, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 300, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "ssoSessionIdleTimeoutRememberMe": 0, + "ssoSessionMaxLifespanRememberMe": 0, + "offlineSessionIdleTimeout": 2592000, + "offlineSessionMaxLifespanEnabled": false, + "offlineSessionMaxLifespan": 5184000, + "clientSessionIdleTimeout": 0, + "clientSessionMaxLifespan": 0, + "clientOfflineSessionIdleTimeout": 0, + "clientOfflineSessionMaxLifespan": 0, + "accessCodeLifespan": 60, + "accessCodeLifespanUserAction": 300, + "accessCodeLifespanLogin": 1800, + "actionTokenGeneratedByAdminLifespan": 43200, + "actionTokenGeneratedByUserLifespan": 300, + "oauth2DeviceCodeLifespan": 600, + "oauth2DevicePollingInterval": 5, + "enabled": true, + "sslRequired": "external", + "registrationAllowed": true, + "registrationEmailAsUsername": false, + "rememberMe": true, + "verifyEmail": false, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": true, + "editUsernameAllowed": false, + "bruteForceProtected": false, + "permanentLockout": false, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "users": [ + { + "username": "conversations", + "email": "conversations@conversations.world", + "firstName": "John", + "lastName": "Doe", + "enabled": true, + "credentials": [ + { + "type": "password", + "value": "conversations" + } + ], + "realmRoles": ["user"] + }, + { + "username": "user-e2e-chromium", + "email": "user@chromium.test", + "firstName": "E2E", + "lastName": "Chromium", + "enabled": true, + "credentials": [ + { + "type": "password", + "value": "password-e2e-chromium" + } + ], + "realmRoles": ["user"] + }, + { + "username": "user-e2e-webkit", + "email": "user@webkit.test", + "firstName": "E2E", + "lastName": "Webkit", + "enabled": true, + "credentials": [ + { + "type": "password", + "value": "password-e2e-webkit" + } + ], + "realmRoles": ["user"] + }, + { + "username": "user-e2e-firefox", + "email": "user@firefox.test", + "firstName": "E2E", + "lastName": "Firefox", + "enabled": true, + "credentials": [ + { + "type": "password", + "value": "password-e2e-firefox" + } + ], + "realmRoles": ["user"] + } + ], + "roles": { + "realm": [ + { + "id": "1f116065-05b6-4269-80a6-c7d904b584b7", + "name": "uma_authorization", + "description": "${role_uma_authorization}", + "composite": false, + "clientRole": false, + "containerId": "ccf4fd40-4286-474d-854a-4714282a8bec", + "attributes": {} + }, + { + "id": "1bfe401a-08fc-4d94-80e0-86c4f5195f99", + "name": "default-roles-conversations", + "description": "${role_default-roles}", + "composite": true, + "composites": { + "realm": ["offline_access", "uma_authorization"], + "client": { + "account": ["view-profile", "manage-account"] + } + }, + "clientRole": false, + "containerId": "ccf4fd40-4286-474d-854a-4714282a8bec", + "attributes": {} + }, + { + "id": "8733db03-278a-45ad-a25e-c167fbd95b5a", + "name": "offline_access", + "description": "${role_offline-access}", + "composite": false, + "clientRole": false, + "containerId": "ccf4fd40-4286-474d-854a-4714282a8bec", + "attributes": {} + } + ], + "client": { + "realm-management": [ + { + "id": "9dcc0883-e2e5-4671-9159-402bdbe73c57", + "name": "impersonation", + "description": "${role_impersonation}", + "composite": false, + "clientRole": true, + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "ae911be0-ea2e-466d-93e0-f8e73fa8f444", + "name": "view-authorization", + "description": "${role_view-authorization}", + "composite": false, + "clientRole": true, + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "e777d332-7205-4b76-8b21-9191a2e85a0d", + "name": "manage-authorization", + "description": "${role_manage-authorization}", + "composite": false, + "clientRole": true, + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "b1a95608-d518-4ede-936e-525ab704d363", + "name": "create-client", + "description": "${role_create-client}", + "composite": false, + "clientRole": true, + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "ac58976a-ae55-4d92-a864-b33e21b07c54", + "name": "view-events", + "description": "${role_view-events}", + "composite": false, + "clientRole": true, + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "a149b28f-d252-4ceb-8ba9-8161603c4184", + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "00a5b886-7ca4-4fba-90c6-a9071e697d86", + "name": "manage-clients", + "description": "${role_manage-clients}", + "composite": false, + "clientRole": true, + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "b22d5cc1-879e-4405-8345-cc204fd0fec0", + "name": "realm-admin", + "description": "${role_realm-admin}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "impersonation", + "view-authorization", + "manage-authorization", + "create-client", + "view-events", + "manage-identity-providers", + "manage-clients", + "view-identity-providers", + "query-users", + "manage-users", + "view-clients", + "view-users", + "manage-events", + "view-realm", + "query-realms", + "query-groups", + "manage-realm", + "query-clients" + ] + } + }, + "clientRole": true, + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "b3e9faf6-17bf-4f62-abd5-07837806a7e6", + "name": "view-identity-providers", + "description": "${role_view-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "a8d85f42-023b-48dd-8f49-c9da2b5317ee", + "name": "query-users", + "description": "${role_query-users}", + "composite": false, + "clientRole": true, + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "eb325a4d-db7a-4f6a-a88b-0ff8aa38b0a5", + "name": "manage-users", + "description": "${role_manage-users}", + "composite": false, + "clientRole": true, + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "267bb612-62f4-4354-abb2-ac6a34bd854b", + "name": "view-clients", + "description": "${role_view-clients}", + "composite": true, + "composites": { + "client": { + "realm-management": ["query-clients"] + } + }, + "clientRole": true, + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "b575be2b-e250-4000-b75e-3038cda8c0dd", + "name": "manage-events", + "description": "${role_manage-events}", + "composite": false, + "clientRole": true, + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "e19cd0bf-8da0-457d-b630-454c611bc1ba", + "name": "view-users", + "description": "${role_view-users}", + "composite": true, + "composites": { + "client": { + "realm-management": ["query-users", "query-groups"] + } + }, + "clientRole": true, + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "c12145cc-cbdc-4ef3-9774-19b1852811ba", + "name": "query-realms", + "description": "${role_query-realms}", + "composite": false, + "clientRole": true, + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "e7e15b84-4971-4c13-be93-315bb36d30e1", + "name": "view-realm", + "description": "${role_view-realm}", + "composite": false, + "clientRole": true, + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "e03d2989-a620-4918-85ed-3eabd0373bb4", + "name": "query-groups", + "description": "${role_query-groups}", + "composite": false, + "clientRole": true, + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "daf8d347-4b30-41d6-a431-7b3723dd8e6f", + "name": "manage-realm", + "description": "${role_manage-realm}", + "composite": false, + "clientRole": true, + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "432cd3eb-4741-46ba-938a-94ff9dece315", + "name": "query-clients", + "description": "${role_query-clients}", + "composite": false, + "clientRole": true, + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + } + ], + "security-admin-console": [], + "admin-cli": [], + "account-console": [], + "broker": [ + { + "id": "2e713186-38da-44d7-a5a5-19d91ef2dfca", + "name": "read-token", + "description": "${role_read-token}", + "composite": false, + "clientRole": true, + "containerId": "41dd8f26-46c2-471a-859e-01886f972ff9", + "attributes": {} + } + ], + "conversations": [], + "account": [ + { + "id": "63b1a4e1-a594-4571-99c3-7c5c3efd61ce", + "name": "manage-consent", + "description": "${role_manage-consent}", + "composite": true, + "composites": { + "client": { + "account": ["view-consent"] + } + }, + "clientRole": true, + "containerId": "06721011-1061-4ca7-944f-be2a20719e20", + "attributes": {} + }, + { + "id": "36ef5fd6-1167-4ba0-9171-c8cb6cfe904b", + "name": "view-groups", + "description": "${role_view-groups}", + "composite": false, + "clientRole": true, + "containerId": "06721011-1061-4ca7-944f-be2a20719e20", + "attributes": {} + }, + { + "id": "f984654a-fca5-45d9-bb47-73009eb9bcf0", + "name": "view-profile", + "description": "${role_view-profile}", + "composite": false, + "clientRole": true, + "containerId": "06721011-1061-4ca7-944f-be2a20719e20", + "attributes": {} + }, + { + "id": "d54168c5-58a5-4f13-9fa8-6dbbee0e4b73", + "name": "manage-account", + "description": "${role_manage-account}", + "composite": true, + "composites": { + "client": { + "account": ["manage-account-links"] + } + }, + "clientRole": true, + "containerId": "06721011-1061-4ca7-944f-be2a20719e20", + "attributes": {} + }, + { + "id": "092b6808-1ee2-44be-9b5d-085ccd6862b4", + "name": "manage-account-links", + "description": "${role_manage-account-links}", + "composite": false, + "clientRole": true, + "containerId": "06721011-1061-4ca7-944f-be2a20719e20", + "attributes": {} + }, + { + "id": "ddd57af0-2a5e-4f9d-98e5-ec96c8d852ce", + "name": "view-applications", + "description": "${role_view-applications}", + "composite": false, + "clientRole": true, + "containerId": "06721011-1061-4ca7-944f-be2a20719e20", + "attributes": {} + }, + { + "id": "84c7324a-4724-41fe-8bd4-848ce5cebd5b", + "name": "view-consent", + "description": "${role_view-consent}", + "composite": false, + "clientRole": true, + "containerId": "06721011-1061-4ca7-944f-be2a20719e20", + "attributes": {} + }, + { + "id": "20d06f75-ea65-4b99-b9ef-2384ffd1de53", + "name": "delete-account", + "description": "${role_delete-account}", + "composite": false, + "clientRole": true, + "containerId": "06721011-1061-4ca7-944f-be2a20719e20", + "attributes": {} + } + ] + } + }, + "groups": [], + "defaultRole": { + "id": "1bfe401a-08fc-4d94-80e0-86c4f5195f99", + "name": "default-roles-conversations", + "description": "${role_default-roles}", + "composite": true, + "clientRole": false, + "containerId": "ccf4fd40-4286-474d-854a-4714282a8bec" + }, + "requiredCredentials": ["password"], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpPolicyCodeReusable": false, + "otpSupportedApplications": ["totpAppGoogleName", "totpAppFreeOTPName"], + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": ["ES256"], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyAcceptableAaguids": [], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": ["ES256"], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": ["offline_access"] + } + ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": ["manage-account", "view-groups"] + } + ] + }, + "clients": [ + { + "id": "06721011-1061-4ca7-944f-be2a20719e20", + "clientId": "account", + "name": "${client_account}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/conversations/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": ["/realms/conversations/account/*"], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "987e14a5-caed-40a6-8bac-8c429b74ca48", + "clientId": "account-console", + "name": "${client_account-console}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/conversations/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": ["/realms/conversations/account/*"], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "4f958126-eaa1-46d5-967a-3a3c2e2d11f7", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "92da37ad-e8a1-41f1-93c6-541dffa7d601", + "clientId": "admin-cli", + "name": "${client_admin-cli}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "41dd8f26-46c2-471a-859e-01886f972ff9", + "clientId": "broker", + "name": "${client_broker}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "869481d0-5774-4e64-bc30-fedc7c58958f", + "clientId": "conversations", + "name": "", + "description": "", + "rootUrl": "", + "adminUrl": "", + "baseUrl": "", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "ThisIsAnExampleKeyForDevPurposeOnly", + "redirectUris": [ + "http://localhost:8070/*", + "http://localhost:8071/*", + "http://localhost:3200/*", + "http://localhost:8088/*", + "http://localhost:3000/*" + ], + "webOrigins": [ + "http://localhost:3200", + "http://localhost:8088", + "http://localhost:8070", + "http://localhost:3000" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": true, + "protocol": "openid-connect", + "attributes": { + "access.token.lifespan": "-1", + "client.secret.creation.time": "1707820779", + "user.info.response.signature.alg": "RS256", + "post.logout.redirect.uris": "http://localhost:8070/*##http://localhost:3200/*##http://localhost:3000/*", + "oauth2.device.authorization.grant.enabled": "false", + "use.jwks.url": "false", + "backchannel.logout.revoke.offline.tokens": "false", + "use.refresh.tokens": "true", + "tls-client-certificate-bound-access-tokens": "false", + "oidc.ciba.grant.enabled": "false", + "backchannel.logout.session.required": "true", + "client_credentials.use_refresh_token": "false", + "acr.loa.map": "{}", + "require.pushed.authorization.requests": "false", + "display.on.consent.screen": "false", + "client.session.idle.timeout": "-1", + "token.response.type.bearer.lower-case": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "clientId": "realm-management", + "name": "${client_realm-management}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "2a4e007a-2fc4-4f43-aace-b93aec9221b4", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/conversations/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": ["/admin/conversations/console/*"], + "webOrigins": ["+"], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "4913be96-5827-46a4-9909-562c2dd5bef6", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "74aeb8e2-a1b6-4897-9eaf-d922becea170", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "consent.screen.text": "${rolesScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "994b8f5e-dfc1-4154-a936-347336e6422a", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "d853f97e-80f8-470e-8447-815b289d9ae3", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + }, + { + "id": "26a9f3ef-cff0-4dee-9fe9-778cd1d2a771", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + } + ] + }, + { + "id": "af52ccc3-4ecb-49b4-9a67-5d4172f16070", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "efb82630-8835-4de0-944e-ac5ea51eca48", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "2256189a-7970-4244-b496-64cbba3ce582", + "name": "acr", + "description": "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "6d7f8b9e-997e-40f8-bae5-83d2647fbeff", + "name": "acr loa level", + "protocol": "openid-connect", + "protocolMapper": "oidc-acr-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + } + ] + }, + { + "id": "b83cebb6-f086-48e2-8e5a-9802736342f2", + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "id": "b99113c6-ccfb-43d4-acd1-09dd34cdf5bc", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${addressScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "696211d7-c434-495f-b3a0-a1b88bebfd6e", + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + }, + { + "id": "16845bd9-5626-4484-b4c5-00af52d8ad8b", + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false", + "consent.screen.text": "" + }, + "protocolMappers": [ + { + "id": "5828a7d9-cdc7-456b-a747-16bf83c2f57d", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": {} + } + ] + }, + { + "id": "ce289e05-eca4-4323-b457-822d39cc6d49", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${profileScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "abe63488-9a39-4e29-a0a8-824db0887b60", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "id": "15690cfb-e14c-46e8-8494-22a0365a4b0c", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + }, + { + "id": "03cf0e4c-c2a5-4203-88c4-5391d361ba15", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "id": "23b1a1da-2ecc-4db7-8d33-4e9233a81e89", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "long" + } + }, + { + "id": "26a72777-56eb-4b46-acca-eca8168e29fc", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "id": "4ae1896b-ea82-4604-8f0e-72133fdee05c", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "id": "79712bcf-b7f7-4ca3-b97c-418f48fded9b", + "name": "first name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "first_name", + "jsonType.label": "String" + } + }, + { + "id": "6397c5e9-95ea-4c31-bd44-a8acf1d18472", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "id": "7f741e96-41fe-4021-bbfd-506e7eb94e69", + "name": "last name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "last_name", + "jsonType.label": "String" + } + }, + { + "id": "5ca62964-2d04-4e8e-963d-e3b08cf32d7c", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "id": "954a5dff-cc19-4dde-b996-787f767db4cc", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "1eba19bf-6fa1-4608-ad2d-d4346580c93d", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "id": "e7bdd267-fcce-451f-b3e1-a775cf611dd2", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "id": "a9a8918c-af00-48a5-a8b3-a28a83653f71", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "cd725067-b6ba-42f1-a940-97a16a23cb85", + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "a4e1812c-4093-4666-a6b3-03c5d9b5ca9f", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + }, + { + "id": "d6690292-74d1-48ac-855d-2f0f3799829e", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "multivalued": "true", + "userinfo.token.claim": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "ce8f1215-0462-4e87-8a3b-18488aee0267", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${phoneScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "0ce95430-80aa-4dd6-994b-5a67302ba531", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + }, + { + "id": "8da0d3b1-d609-417e-9adc-1de77549baf9", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + } + ] + }, + { + "id": "f89a9158-7c03-49b0-8a3c-d0b75e2ce1b4", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${emailScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "fb109597-e31e-46d7-84c5-62e5fcf32ac8", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + }, + { + "id": "qb109597-e31e-46d7-7844-62e5fcf32ac8", + "name": "email sub", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "sub", + "jsonType.label": "String" + } + }, + { + "id": "61c135e5-2447-494b-bc70-9612f383be27", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + } + ] + } + ], + "defaultDefaultClientScopes": [ + "role_list", + "profile", + "email", + "roles", + "web-origins", + "acr" + ], + "defaultOptionalClientScopes": [ + "offline_access", + "address", + "phone", + "microprofile-jwt" + ], + "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", + "xContentTypeOptions": "nosniff", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection": "1; mode=block", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "smtpServer": {}, + "eventsEnabled": false, + "eventsListeners": ["jboss-logging"], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "identityProviders": [], + "identityProviderMappers": [], + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "74dffa9a-5d4f-4ce3-9708-885212f56861", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "48096073-ceae-4e68-a15b-f1aa390dcce5", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": ["true"] + } + }, + { + "id": "51b0e87c-ee04-4664-a299-f8e49cb7a9ac", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": ["200"] + } + }, + { + "id": "6379b091-2289-4fe7-894c-c03f1bd0e69b", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": ["true"] + } + }, + { + "id": "97ae8320-a439-463b-817e-05bd4a6c39d1", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-role-list-mapper", + "oidc-usermodel-attribute-mapper", + "saml-user-attribute-mapper", + "saml-user-property-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-full-name-mapper", + "oidc-address-mapper", + "oidc-usermodel-property-mapper" + ] + } + }, + { + "id": "49131ffc-4831-4e3e-a466-f9f08aa1bee0", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "e12647d2-e21f-49bc-a8c6-28154c5544d2", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-user-property-mapper", + "saml-user-attribute-mapper", + "oidc-address-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-usermodel-property-mapper", + "oidc-full-name-mapper", + "saml-role-list-mapper", + "oidc-usermodel-attribute-mapper" + ] + } + }, + { + "id": "c9f00ef2-00d9-44bd-9b6c-3b3bf57e44ba", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": ["true"], + "client-uris-must-match": ["true"] + } + } + ], + "org.keycloak.userprofile.UserProfileProvider": [ + { + "id": "96260850-72a5-4b49-b96b-5a33d0b5337d", + "providerId": "declarative-user-profile", + "subComponents": {}, + "config": {} + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "55d93b4d-fe05-46a1-a832-36f380aaddf7", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "priority": ["100"] + } + }, + { + "id": "bee288b4-ecdf-4ec4-8c31-ee330f1e8f95", + "name": "hmac-generated", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "priority": ["100"], + "algorithm": ["HS256"] + } + }, + { + "id": "2aa8f54d-8b4b-4eb7-a05b-89211f544358", + "name": "rsa-enc-generated", + "providerId": "rsa-enc-generated", + "subComponents": {}, + "config": { + "priority": ["100"], + "algorithm": ["RSA-OAEP"] + } + }, + { + "id": "23ad48f4-2275-4a0d-aa0d-1e0691f9c620", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "priority": ["100"] + } + } + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "0c349304-21fd-47ff-8dc6-46efb107b7e9", + "alias": "Account verification options", + "description": "Method with which to verity the existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-email-verification", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Verify Existing Account by Re-authentication", + "userSetupAllowed": false + } + ] + }, + { + "id": "cf1ed416-7274-4804-88bf-4261b0bacdc6", + "alias": "Authentication Options", + "description": "Authentication options.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "basic-auth", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "basic-auth-otp", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "d949f1f1-4622-49ec-b74a-4b8a58c653d2", + "alias": "Browser - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "3deb6d9d-2064-410c-af99-b1601cd9b1c4", + "alias": "Direct Grant - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "f777c4be-f7d1-453e-a9d7-a2a235b7975b", + "alias": "First broker login - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "1bc12f49-e2ef-42bd-959a-0983e1cd4d65", + "alias": "Handle Existing Account", + "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-confirm-link", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Account verification options", + "userSetupAllowed": false + } + ] + }, + { + "id": "324cdcf5-8f31-4768-9db9-63208f182b39", + "alias": "Reset - Conditional OTP", + "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "23d17138-8ebd-4195-91d3-614094f62070", + "alias": "User creation or linking", + "description": "Flow for the existing/non-existing user alternatives", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "create unique user config", + "authenticator": "idp-create-user-if-unique", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Handle Existing Account", + "userSetupAllowed": false + } + ] + }, + { + "id": "61fec72a-bfd2-42e8-95c1-fa0b76c1cd2b", + "alias": "Verify Existing Account by Re-authentication", + "description": "Reauthentication of existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "First broker login - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "dc00b9a8-fc37-4591-a1ea-07c7f884d394", + "alias": "browser", + "description": "browser based authentication", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "identity-provider-redirector", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 25, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "forms", + "userSetupAllowed": false + } + ] + }, + { + "id": "4f27245a-49b8-4870-a5e2-f0ea624a792c", + "alias": "clients", + "description": "Base authentication for clients", + "providerId": "client-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "client-secret", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-secret-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-x509", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 40, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "5b2c66e1-7bbf-4707-9db8-244269b68164", + "alias": "direct grant", + "description": "OpenID Connect Resource Owner Grant", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "direct-grant-validate-username", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "Direct Grant - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "4bcddec4-4260-4f4f-a757-3aff9b1d30f3", + "alias": "docker auth", + "description": "Used by Docker clients to authenticate against the IDP", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "docker-http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "04a94e38-b7fb-48f6-8d63-5640f835c619", + "alias": "first broker login", + "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "review profile config", + "authenticator": "idp-review-profile", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "User creation or linking", + "userSetupAllowed": false + } + ] + }, + { + "id": "bfcf5112-96ac-485a-8663-b02ad41af919", + "alias": "forms", + "description": "Username, password, otp and other auth forms.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Browser - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "e262d10d-ad0d-4d18-bc05-3a44f7d21736", + "alias": "http challenge", + "description": "An authentication flow based on challenge-response HTTP Authentication Schemes", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "no-cookie-redirect", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Authentication Options", + "userSetupAllowed": false + } + ] + }, + { + "id": "b671c4b3-22b6-4aac-a1d1-464a2101767c", + "alias": "registration", + "description": "registration flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-page-form", + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": true, + "flowAlias": "registration form", + "userSetupAllowed": false + } + ] + }, + { + "id": "f570e064-0e62-4eae-8087-8b06751b8f33", + "alias": "registration form", + "description": "registration form", + "providerId": "form-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-user-creation", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-profile-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 40, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-password-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 50, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-recaptcha-action", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 60, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "07124099-1d10-4148-ac06-4b0b700908da", + "alias": "reset credentials", + "description": "Reset credentials for a user if they forgot their password or something", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "reset-credentials-choose-user", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-credential-email", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 40, + "autheticatorFlow": true, + "flowAlias": "Reset - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "0a5fa089-f987-4903-9170-36565edda152", + "alias": "saml ecp", + "description": "SAML ECP Profile Authentication Flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + } + ], + "authenticatorConfig": [ + { + "id": "d2818365-2189-4003-9817-0ad5368e37f3", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "72508559-0176-4eee-a77e-0795d652be12", + "alias": "review profile config", + "config": { + "update.profile.on.first.login": "missing" + } + } + ], + "requiredActions": [ + { + "alias": "CONFIGURE_TOTP", + "name": "Configure OTP", + "providerId": "CONFIGURE_TOTP", + "enabled": true, + "defaultAction": false, + "priority": 10, + "config": {} + }, + { + "alias": "terms_and_conditions", + "name": "Terms and Conditions", + "providerId": "terms_and_conditions", + "enabled": false, + "defaultAction": false, + "priority": 20, + "config": {} + }, + { + "alias": "UPDATE_PASSWORD", + "name": "Update Password", + "providerId": "UPDATE_PASSWORD", + "enabled": true, + "defaultAction": false, + "priority": 30, + "config": {} + }, + { + "alias": "UPDATE_PROFILE", + "name": "Update Profile", + "providerId": "UPDATE_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 40, + "config": {} + }, + { + "alias": "VERIFY_EMAIL", + "name": "Verify Email", + "providerId": "VERIFY_EMAIL", + "enabled": true, + "defaultAction": false, + "priority": 50, + "config": {} + }, + { + "alias": "delete_account", + "name": "Delete Account", + "providerId": "delete_account", + "enabled": false, + "defaultAction": false, + "priority": 60, + "config": {} + }, + { + "alias": "CONFIGURE_RECOVERY_AUTHN_CODES", + "name": "Recovery Authentication Codes", + "providerId": "CONFIGURE_RECOVERY_AUTHN_CODES", + "enabled": true, + "defaultAction": false, + "priority": 70, + "config": {} + }, + { + "alias": "UPDATE_EMAIL", + "name": "Update Email", + "providerId": "UPDATE_EMAIL", + "enabled": true, + "defaultAction": false, + "priority": 70, + "config": {} + }, + { + "alias": "webauthn-register", + "name": "Webauthn Register", + "providerId": "webauthn-register", + "enabled": true, + "defaultAction": false, + "priority": 70, + "config": {} + }, + { + "alias": "webauthn-register-passwordless", + "name": "Webauthn Register Passwordless", + "providerId": "webauthn-register-passwordless", + "enabled": true, + "defaultAction": false, + "priority": 80, + "config": {} + }, + { + "alias": "update_user_locale", + "name": "Update User Locale", + "providerId": "update_user_locale", + "enabled": true, + "defaultAction": false, + "priority": 1000, + "config": {} + } + ], + "browserFlow": "browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "dockerAuthenticationFlow": "docker auth", + "attributes": { + "cibaBackchannelTokenDeliveryMode": "poll", + "cibaExpiresIn": "120", + "cibaAuthRequestedUserHint": "login_hint", + "oauth2DeviceCodeLifespan": "600", + "oauth2DevicePollingInterval": "5", + "clientOfflineSessionMaxLifespan": "0", + "clientSessionIdleTimeout": "0", + "parRequestUriLifespan": "60", + "clientSessionMaxLifespan": "0", + "clientOfflineSessionIdleTimeout": "0", + "cibaInterval": "5", + "realmReusableOtpCode": "false" + }, + "keycloakVersion": "20.0.1", + "userManagedAccessAllowed": false, + "clientProfiles": { + "profiles": [] + }, + "clientPolicies": { + "policies": [] + } +} diff --git a/docker/files/etc/nginx/conf.d/default.conf b/docker/files/etc/nginx/conf.d/default.conf new file mode 100644 index 0000000..0c0255f --- /dev/null +++ b/docker/files/etc/nginx/conf.d/default.conf @@ -0,0 +1,52 @@ + +server { + listen 8083; + server_name localhost; + charset utf-8; + + # Proxy auth for media + location /media/ { + # Auth request configuration + auth_request /media-auth; + auth_request_set $authHeader $upstream_http_authorization; + auth_request_set $authDate $upstream_http_x_amz_date; + auth_request_set $authContentSha256 $upstream_http_x_amz_content_sha256; + + # Pass specific headers from the auth response + proxy_set_header Authorization $authHeader; + proxy_set_header X-Amz-Date $authDate; + proxy_set_header X-Amz-Content-SHA256 $authContentSha256; + + # Get resource from Minio + proxy_pass http://minio:9000/conversations-media-storage/; + proxy_set_header Host minio:9000; + + add_header Content-Security-Policy "default-src 'none'" always; + } + + location /media-auth { + proxy_pass http://app-dev:8000/api/v1.0/documents/media-auth/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Original-URL $request_uri; + + # Prevent the body from being passed + proxy_pass_request_body off; + proxy_set_header Content-Length ""; + proxy_set_header X-Original-Method $request_method; + } + + location / { + proxy_pass http://keycloak:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # Increase proxy buffer size to allow keycloak to send large + # header responses when a user is created. + proxy_buffer_size 128k; + proxy_buffers 4 256k; + proxy_busy_buffers_size 256k; + } +} diff --git a/docker/files/usr/local/bin/entrypoint b/docker/files/usr/local/bin/entrypoint new file mode 100755 index 0000000..0f1d44c --- /dev/null +++ b/docker/files/usr/local/bin/entrypoint @@ -0,0 +1,35 @@ +#!/bin/sh +# +# The container user (see USER in the Dockerfile) is an un-privileged user that +# does not exists and is not created during the build phase (see Dockerfile). +# Hence, we use this entrypoint to wrap commands that will be run in the +# container to create an entry for this user in the /etc/passwd file. +# +# The following environment variables may be passed to the container to +# customize running user account: +# +# * USER_NAME: container user name (default: default) +# * HOME : container user home directory (default: none) +# +# To pass environment variables, you can either use the -e option of the docker run command: +# +# docker run --rm -e USER_NAME=foo -e HOME='/home/foo' conversations:latest python manage.py migrate +# +# or define new variables in an environment file to use with docker or docker compose: +# +# # env.d/production +# USER_NAME=foo +# HOME=/home/foo +# +# docker run --rm --env-file env.d/production conversations:latest python manage.py migrate +# + +echo "๐Ÿณ(entrypoint) creating user running in the container..." +if ! whoami > /dev/null 2>&1; then + if [ -w /etc/passwd ]; then + echo "${USER_NAME:-default}:x:$(id -u):$(id -g):${USER_NAME:-default} user:${HOME}:/sbin/nologin" >> /etc/passwd + fi +fi + +echo "๐Ÿณ(entrypoint) running your command: ${*}" +exec "$@" diff --git a/docker/files/usr/local/etc/gunicorn/conversations.py b/docker/files/usr/local/etc/gunicorn/conversations.py new file mode 100644 index 0000000..0d95de8 --- /dev/null +++ b/docker/files/usr/local/etc/gunicorn/conversations.py @@ -0,0 +1,16 @@ +# Gunicorn-django settings +bind = ["0.0.0.0:8000"] +name = "conversations" +python_path = "/app" + +# Run +graceful_timeout = 90 +timeout = 90 +workers = 3 + +# Logging +# Using '-' for the access log file makes gunicorn log accesses to stdout +accesslog = "-" +# Using '-' for the error log file makes gunicorn log errors to stderr +errorlog = "-" +loglevel = "info" diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..230d324 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,19 @@ +## Architecture + +### Global system architecture + +```mermaid +flowchart TD + User -- HTTP --> Front("Frontend (NextJS SPA)") + Front -- REST API --> Back("Backend (Django)") + Front -- WebSocket --> Yserver("Microservice Yjs (Express)") -- WebSocket --> CollaborationServer("Collaboration server (Hocuspocus)") -- REST API <--> Back + Front -- OIDC --> Back -- OIDC ---> OIDC("Keycloak / ProConnect") + Back -- REST API --> Yserver + Back --> DB("Database (PostgreSQL)") + Back <--> Celery --> DB + Back ----> S3("Minio (S3)") +``` + +### Architecture decision records + +- [ADR-0001-20250106-use-yjs-for-docs-editing](./adr/ADR-0001-20250106-use-yjs-for-docs-editing.md) \ No newline at end of file diff --git a/docs/assets/logo.png b/docs/assets/logo.png new file mode 100644 index 0000000..949969f Binary files /dev/null and b/docs/assets/logo.png differ diff --git a/docs/env.md b/docs/env.md new file mode 100644 index 0000000..73ccd78 --- /dev/null +++ b/docs/env.md @@ -0,0 +1,127 @@ +# Conversations variables + +Here we describe all environment variables that can be set for the conversations application. + +## conversations-backend container + +These are the environment variables you can set for the `conversations-backend` container. + +| Option | Description | default | +|-------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------| +| DJANGO_ALLOWED_HOSTS | allowed hosts | [] | +| DJANGO_SECRET_KEY | secret key | | +| DJANGO_SERVER_TO_SERVER_API_TOKENS | | [] | +| DB_ENGINE | engine to use for database connections | django.db.backends.postgresql_psycopg2 | +| DB_NAME | name of the database | conversations | +| DB_USER | user to authenticate with | dinum | +| DB_PASSWORD | password to authenticate with | pass | +| DB_HOST | host of the database | localhost | +| DB_PORT | port of the database | 5432 | +| MEDIA_BASE_URL | | | +| STORAGES_STATICFILES_BACKEND | | whitenoise.storage.CompressedManifestStaticFilesStorage | +| AWS_S3_ENDPOINT_URL | S3 endpoint | | +| AWS_S3_ACCESS_KEY_ID | access id for s3 endpoint | | +| AWS_S3_SECRET_ACCESS_KEY | access key for s3 endpoint | | +| AWS_S3_REGION_NAME | region name for s3 endpoint | | +| AWS_STORAGE_BUCKET_NAME | bucket name for s3 endpoint | conversations-media-storage | +| DOCUMENT_IMAGE_MAX_SIZE | maximum size of document in bytes | 10485760 | +| LANGUAGE_CODE | default language | en-us | +| API_USERS_LIST_THROTTLE_RATE_SUSTAINED | throttle rate for api | 180/hour | +| API_USERS_LIST_THROTTLE_RATE_BURST | throttle rate for api on burst | 30/minute | +| SPECTACULAR_SETTINGS_ENABLE_DJANGO_DEPLOY_CHECK | | false | +| TRASHBIN_CUTOFF_DAYS | trashbin cutoff | 30 | +| DJANGO_EMAIL_BACKEND | email backend library | django.core.mail.backends.smtp.EmailBackend | +| DJANGO_EMAIL_BRAND_NAME | brand name for email | | +| DJANGO_EMAIL_HOST | host name of email | | +| DJANGO_EMAIL_HOST_USER | user to authenticate with on the email host | | +| DJANGO_EMAIL_HOST_PASSWORD | password to authenticate with on the email host | | +| DJANGO_EMAIL_LOGO_IMG | logo for the email | | +| DJANGO_EMAIL_PORT | port used to connect to email host | | +| DJANGO_EMAIL_USE_TLS | use tls for email host connection | false | +| DJANGO_EMAIL_USE_SSL | use sstl for email host connection | false | +| DJANGO_EMAIL_FROM | email address used as sender | from@example.com | +| DJANGO_CORS_ALLOW_ALL_ORIGINS | allow all CORS origins | false | +| DJANGO_CORS_ALLOWED_ORIGINS | list of origins allowed for CORS | [] | +| DJANGO_CORS_ALLOWED_ORIGIN_REGEXES | list of origins allowed for CORS using regulair expressions | [] | +| SENTRY_DSN | sentry host | | +| FRONTEND_CSS_URL | To add a external css file to the app | | +| FRONTEND_HOMEPAGE_FEATURE_ENABLED | frontend feature flag to display the homepage | false | +| FRONTEND_THEME | frontend theme to use | | +| POSTHOG_KEY | posthog key for analytics | | +| CRISP_WEBSITE_ID | crisp website id for support | | +| DJANGO_CELERY_BROKER_URL | celery broker url | redis://redis:6379/0 | +| DJANGO_CELERY_BROKER_TRANSPORT_OPTIONS | celery broker transport options | {} | +| SESSION_COOKIE_AGE | duration of the cookie session | 60*60*12 | +| OIDC_CREATE_USER | create used on OIDC | false | +| OIDC_RP_SIGN_ALGO | verification algorithm used OIDC tokens | RS256 | +| OIDC_RP_CLIENT_ID | client id used for OIDC | conversations | +| OIDC_RP_CLIENT_SECRET | client secret used for OIDC | | +| OIDC_OP_JWKS_ENDPOINT | JWKS endpoint for OIDC | | +| OIDC_OP_AUTHORIZATION_ENDPOINT | Authorization endpoint for OIDC | | +| OIDC_OP_TOKEN_ENDPOINT | Token endpoint for OIDC | | +| OIDC_OP_USER_ENDPOINT | User endpoint for OIDC | | +| OIDC_OP_LOGOUT_ENDPOINT | Logout endpoint for OIDC | | +| OIDC_AUTH_REQUEST_EXTRA_PARAMS | OIDC extra auth parameters | {} | +| OIDC_RP_SCOPES | scopes requested for OIDC | openid email | +| LOGIN_REDIRECT_URL | login redirect url | | +| LOGIN_REDIRECT_URL_FAILURE | login redirect url on failure | | +| LOGOUT_REDIRECT_URL | logout redirect url | | +| OIDC_USE_NONCE | use nonce for OIDC | true | +| OIDC_REDIRECT_REQUIRE_HTTPS | Require https for OIDC redirect url | false | +| OIDC_REDIRECT_ALLOWED_HOSTS | Allowed hosts for OIDC redirect url | [] | +| OIDC_STORE_ID_TOKEN | Store OIDC token | true | +| OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION | faillback to email for identification | true | +| OIDC_ALLOW_DUPLICATE_EMAILS | Allow duplicate emails | false | +| USER_OIDC_ESSENTIAL_CLAIMS | essential claims in OIDC token | [] | +| OIDC_USERINFO_FULLNAME_FIELDS | OIDC token claims to create full name | ["first_name", "last_name"] | +| OIDC_USERINFO_SHORTNAME_FIELD | OIDC token claims to create shortname | first_name | +| ALLOW_LOGOUT_GET_METHOD | Allow get logout method | true | +| AI_API_KEY | AI key to be used for AI Base url | | +| AI_BASE_URL | OpenAI compatible AI base url | | +| AI_MODEL | AI Model to use | | +| AI_AGENT_NAME | Name of the AI agent (useless) | Conversations Assistant | +| AI_AGENT_INSTRUCTION | Base instruction for the AI agent | You are a helpful assistant | +| Y_PROVIDER_API_KEY | Y provider API key | | +| Y_PROVIDER_API_BASE_URL | Y Provider url | | +| CONVERSION_API_ENDPOINT | Conversion API endpoint | convert-markdown | +| CONVERSION_API_CONTENT_FIELD | Conversion api content field | content | +| CONVERSION_API_TIMEOUT | Conversion api timeout | 30 | +| CONVERSION_API_SECURE | Require secure conversion api | false | +| LOGGING_LEVEL_LOGGERS_ROOT | default logging level. options are "DEBUG", "INFO", "WARN", "ERROR", "CRITICAL" | INFO | +| LOGGING_LEVEL_LOGGERS_APP | application logging level. options are "DEBUG", "INFO", "WARN", "ERROR", "CRITICAL" | INFO | +| API_USERS_LIST_LIMIT | Limit on API users | 5 | +| DJANGO_CSRF_TRUSTED_ORIGINS | CSRF trusted origins | [] | +| REDIS_URL | cache url | redis://redis:6379/1 | +| CACHES_DEFAULT_TIMEOUT | cache default timeout | 30 | +| CACHES_KEY_PREFIX | The prefix used to every cache keys. | conversations | +| THEME_CUSTOMIZATION_FILE_PATH | full path to the file customizing the theme. An example is provided in src/backend/conversations/configuration/theme/default.json | BASE_DIR/conversations/configuration/theme/default.json | +| THEME_CUSTOMIZATION_CACHE_TIMEOUT | Cache duration for the customization settings | 86400 | + + +## conversations-frontend image + +These are the environment variables you can set to build the `conversations-frontend` image. + +Depending on how you are building the front-end application, this variable is used in different ways. + +If you want to build the Docker image, this variable is used as an argument in the build command. + +Example: + +``` +docker build -f src/frontend/Dockerfile --target frontend-production --build-arg API_ORIGIN=https://mybackend.example.com conversations-frontend:latest +``` + +If you want to build the front-end application using the yarn build command, you can edit the file `src/frontend/apps/conversations/.env` with the `NODE_ENV=production` environment variable and modify it. Alternatively, you can use the listed environment variables with the prefix `NEXT_PUBLIC_`. + +Example: + +``` +cd src/frontend/apps/conversations +NODE_ENV=production NEXT_PUBLIC_API_ORIGIN=https://mybackend.example.com yarn build +``` + +| Option | Description | default | +|-------------------------------------------------|------------------------------------------------------------------------------------| ------------------------------------------------------- | +| API_ORIGIN | backend domain - it uses the current domain if not initialized | | +| PRODUCT_NAME | to change the default product name displayed in frontend | Conversations | diff --git a/docs/examples/conversations.values.yaml b/docs/examples/conversations.values.yaml new file mode 100644 index 0000000..9792b41 --- /dev/null +++ b/docs/examples/conversations.values.yaml @@ -0,0 +1,134 @@ +image: + repository: lasuite/conversations-backend + pullPolicy: Always + tag: "latest" + +backend: + replicas: 1 + envVars: + DJANGO_CSRF_TRUSTED_ORIGINS: https://conversations.127.0.0.1.nip.io + DJANGO_CONFIGURATION: Feature + DJANGO_ALLOWED_HOSTS: conversations.127.0.0.1.nip.io + DJANGO_SERVER_TO_SERVER_API_TOKENS: secret-api-key + DJANGO_SECRET_KEY: AgoodOrAbadKey + DJANGO_SETTINGS_MODULE: conversations.settings + DJANGO_SUPERUSER_PASSWORD: admin + DJANGO_EMAIL_BRAND_NAME: "La Suite Numรฉrique" + DJANGO_EMAIL_HOST: "maildev" + DJANGO_EMAIL_LOGO_IMG: https://conversations.127.0.0.1.nip.io/assets/logo-suite-numerique.png + DJANGO_EMAIL_PORT: 1025 + DJANGO_EMAIL_USE_SSL: False + LOGGING_LEVEL_HANDLERS_CONSOLE: ERROR + LOGGING_LEVEL_LOGGERS_ROOT: INFO + LOGGING_LEVEL_LOGGERS_APP: INFO + OIDC_OP_JWKS_ENDPOINT: https://keycloak.127.0.0.1.nip.io/realms/conversations/protocol/openid-connect/certs + OIDC_OP_AUTHORIZATION_ENDPOINT: https://keycloak.127.0.0.1.nip.io/realms/conversations/protocol/openid-connect/auth + OIDC_OP_TOKEN_ENDPOINT: https://keycloak.127.0.0.1.nip.io/realms/conversations/protocol/openid-connect/token + OIDC_OP_USER_ENDPOINT: https://keycloak.127.0.0.1.nip.io/realms/conversations/protocol/openid-connect/userinfo + OIDC_OP_LOGOUT_ENDPOINT: https://keycloak.127.0.0.1.nip.io/realms/conversations/protocol/openid-connect/session/end + OIDC_RP_CLIENT_ID: conversations + OIDC_RP_CLIENT_SECRET: ThisIsAnExampleKeyForDevPurposeOnly + OIDC_RP_SIGN_ALGO: RS256 + OIDC_RP_SCOPES: "openid email" + OIDC_VERIFY_SSL: False + OIDC_USERINFO_SHORTNAME_FIELD: "given_name" + OIDC_USERINFO_FULLNAME_FIELDS: "given_name,usual_name" + OIDC_REDIRECT_ALLOWED_HOSTS: https://conversations.127.0.0.1.nip.io + OIDC_AUTH_REQUEST_EXTRA_PARAMS: "{'acr_values': 'eidas1'}" + LOGIN_REDIRECT_URL: https://conversations.127.0.0.1.nip.io + LOGIN_REDIRECT_URL_FAILURE: https://conversations.127.0.0.1.nip.io + LOGOUT_REDIRECT_URL: https://conversations.127.0.0.1.nip.io + POSTHOG_KEY: "{'id': 'posthog_key', 'host': 'https://product.conversations.127.0.0.1.nip.io'}" + DB_HOST: postgresql + DB_NAME: conversations + DB_USER: dinum + DB_PASSWORD: pass + DB_PORT: 5432 + POSTGRES_DB: conversations + POSTGRES_USER: dinum + POSTGRES_PASSWORD: pass + REDIS_URL: redis://default:pass@redis-master:6379/1 + AWS_S3_ENDPOINT_URL: http://minio.conversations.svc.cluster.local:9000 + AWS_S3_ACCESS_KEY_ID: root + AWS_S3_SECRET_ACCESS_KEY: password + AWS_STORAGE_BUCKET_NAME: conversations-media-storage + STORAGES_STATICFILES_BACKEND: django.contrib.staticfiles.storage.StaticFilesStorage + + migrate: + command: + - "/bin/sh" + - "-c" + - | + python manage.py migrate --no-input && + python manage.py create_demo --force + restartPolicy: Never + + command: + - "gunicorn" + - "-c" + - "/usr/local/etc/gunicorn/conversations.py" + - "conversations.wsgi:application" + - "--reload" + + createsuperuser: + command: + - "/bin/sh" + - "-c" + - | + python manage.py createsuperuser --email admin@example.com --password admin + restartPolicy: Never + + # Extra volume to manage our local custom CA and avoid to set ssl_verify: false + extraVolumeMounts: + - name: certs + mountPath: /usr/local/lib/python3.12/site-packages/certifi/cacert.pem + subPath: cacert.pem + + # Extra volume to manage our local custom CA and avoid to set ssl_verify: false + extraVolumes: + - name: certs + configMap: + name: certifi + items: + - key: cacert.pem + path: cacert.pem +frontend: + envVars: + PORT: 8080 + NEXT_PUBLIC_API_ORIGIN: https://conversations.127.0.0.1.nip.io + + replicas: 1 + + image: + repository: lasuite/conversations-frontend + pullPolicy: Always + tag: "latest" + +posthog: + ingress: + enabled: false + ingressAssets: + enabled: false + +ingress: + enabled: true + host: conversations.127.0.0.1.nip.io + +ingressAdmin: + enabled: true + host: conversations.127.0.0.1.nip.io + +ingressMedia: + enabled: true + host: conversations.127.0.0.1.nip.io + + annotations: + nginx.ingress.kubernetes.io/auth-url: https://conversations.127.0.0.1.nip.io/api/v1.0/documents/media-auth/ + nginx.ingress.kubernetes.io/auth-response-headers: "Authorization, X-Amz-Date, X-Amz-Content-SHA256" + nginx.ingress.kubernetes.io/upstream-vhost: minio.conversations.svc.cluster.local:9000 + nginx.ingress.kubernetes.io/rewrite-target: /conversations-media-storage/$1 + +serviceMedia: + host: minio.conversations.svc.cluster.local + port: 9000 + diff --git a/docs/examples/keycloak.values.yaml b/docs/examples/keycloak.values.yaml new file mode 100644 index 0000000..8bf93f8 --- /dev/null +++ b/docs/examples/keycloak.values.yaml @@ -0,0 +1,2299 @@ +postgresql: + auth: + username: keycloak + password: keycloak + database: keycloak +extraEnvVars: + - name: KEYCLOAK_EXTRA_ARGS + value: "--import-realm" + - name: KC_HOSTNAME_URL + value: https://keycloak.127.0.0.1.nip.io +extraVolumes: + - name: import + configMap: + name: conversations-keycloak +extraVolumeMounts: + - name: import + mountPath: /opt/bitnami/keycloak/data/import/ +auth: + adminUser: su + adminPassword: su +proxy: edge +ingress: + enabled: true + hostname: keycloak.127.0.0.1.nip.io +extraDeploy: +- apiVersion: v1 + kind: ConfigMap + metadata: + name: conversations-keycloak + data: + conversations.json: | + { + "id": "ccf4fd40-4286-474d-854a-4714282a8bec", + "realm": "conversations", + "notBefore": 0, + "defaultSignatureAlgorithm": "RS256", + "revokeRefreshToken": "false", + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 300, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "ssoSessionIdleTimeoutRememberMe": 0, + "ssoSessionMaxLifespanRememberMe": 0, + "offlineSessionIdleTimeout": 2592000, + "offlineSessionMaxLifespanEnabled": "false", + "offlineSessionMaxLifespan": 5184000, + "clientSessionIdleTimeout": 0, + "clientSessionMaxLifespan": 0, + "clientOfflineSessionIdleTimeout": 0, + "clientOfflineSessionMaxLifespan": 0, + "accessCodeLifespan": 60, + "accessCodeLifespanUserAction": 300, + "accessCodeLifespanLogin": 1800, + "actionTokenGeneratedByAdminLifespan": 43200, + "actionTokenGeneratedByUserLifespan": 300, + "oauth2DeviceCodeLifespan": 600, + "oauth2DevicePollingInterval": 5, + "enabled": "true", + "sslRequired": "external", + "registrationAllowed": "true", + "registrationEmailAsUsername": "false", + "rememberMe": "true", + "verifyEmail": "false", + "loginWithEmailAllowed": "true", + "duplicateEmailsAllowed": "false", + "resetPasswordAllowed": "true", + "editUsernameAllowed": "false", + "bruteForceProtected": "false", + "permanentLockout": "false", + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "users": [ + { + "username": "conversations", + "email": "conversations@meet.world", + "firstName": "John", + "lastName": "Doe", + "enabled": "true", + "credentials": [ + { + "type": "password", + "value": "conversations" + } + ], + "realmRoles": ["user"] + }, + { + "username": "user-e2e-chromium", + "email": "user@chromium.test", + "firstName": "E2E", + "lastName": "Chromium", + "enabled": "true", + "credentials": [ + { + "type": "password", + "value": "password-e2e-chromium" + } + ], + "realmRoles": ["user"] + }, + { + "username": "user-e2e-webkit", + "email": "user@webkit.test", + "firstName": "E2E", + "lastName": "Webkit", + "enabled": "true", + "credentials": [ + { + "type": "password", + "value": "password-e2e-webkit" + } + ], + "realmRoles": ["user"] + }, + { + "username": "user-e2e-firefox", + "email": "user@firefox.test", + "firstName": "E2E", + "lastName": "Firefox", + "enabled": "true", + "credentials": [ + { + "type": "password", + "value": "password-e2e-firefox" + } + ], + "realmRoles": ["user"] + } + ], + "roles": { + "realm": [ + { + "id": "1f116065-05b6-4269-80a6-c7d904b584b7", + "name": "uma_authorization", + "description": "${role_uma_authorization}", + "composite": "false", + "clientRole": "false", + "containerId": "ccf4fd40-4286-474d-854a-4714282a8bec", + "attributes": {} + }, + { + "id": "1bfe401a-08fc-4d94-80e0-86c4f5195f99", + "name": "default-roles-conversations", + "description": "${role_default-roles}", + "composite": "true", + "composites": { + "realm": ["offline_access", "uma_authorization"], + "client": { + "account": ["view-profile", "manage-account"] + } + }, + "clientRole": "false", + "containerId": "ccf4fd40-4286-474d-854a-4714282a8bec", + "attributes": {} + }, + { + "id": "8733db03-278a-45ad-a25e-c167fbd95b5a", + "name": "offline_access", + "description": "${role_offline-access}", + "composite": "false", + "clientRole": "false", + "containerId": "ccf4fd40-4286-474d-854a-4714282a8bec", + "attributes": {} + } + ], + "client": { + "realm-management": [ + { + "id": "9dcc0883-e2e5-4671-9159-402bdbe73c57", + "name": "impersonation", + "description": "${role_impersonation}", + "composite": "false", + "clientRole": "true", + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "ae911be0-ea2e-466d-93e0-f8e73fa8f444", + "name": "view-authorization", + "description": "${role_view-authorization}", + "composite": "false", + "clientRole": "true", + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "e777d332-7205-4b76-8b21-9191a2e85a0d", + "name": "manage-authorization", + "description": "${role_manage-authorization}", + "composite": "false", + "clientRole": "true", + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "b1a95608-d518-4ede-936e-525ab704d363", + "name": "create-client", + "description": "${role_create-client}", + "composite": "false", + "clientRole": "true", + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "ac58976a-ae55-4d92-a864-b33e21b07c54", + "name": "view-events", + "description": "${role_view-events}", + "composite": "false", + "clientRole": "true", + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "a149b28f-d252-4ceb-8ba9-8161603c4184", + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", + "composite": "false", + "clientRole": "true", + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "00a5b886-7ca4-4fba-90c6-a9071e697d86", + "name": "manage-clients", + "description": "${role_manage-clients}", + "composite": "false", + "clientRole": "true", + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "b22d5cc1-879e-4405-8345-cc204fd0fec0", + "name": "realm-admin", + "description": "${role_realm-admin}", + "composite": "true", + "composites": { + "client": { + "realm-management": [ + "impersonation", + "view-authorization", + "manage-authorization", + "create-client", + "view-events", + "manage-identity-providers", + "manage-clients", + "view-identity-providers", + "query-users", + "manage-users", + "view-clients", + "view-users", + "manage-events", + "view-realm", + "query-realms", + "query-groups", + "manage-realm", + "query-clients" + ] + } + }, + "clientRole": "true", + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "b3e9faf6-17bf-4f62-abd5-07837806a7e6", + "name": "view-identity-providers", + "description": "${role_view-identity-providers}", + "composite": "false", + "clientRole": "true", + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "a8d85f42-023b-48dd-8f49-c9da2b5317ee", + "name": "query-users", + "description": "${role_query-users}", + "composite": "false", + "clientRole": "true", + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "eb325a4d-db7a-4f6a-a88b-0ff8aa38b0a5", + "name": "manage-users", + "description": "${role_manage-users}", + "composite": "false", + "clientRole": "true", + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "267bb612-62f4-4354-abb2-ac6a34bd854b", + "name": "view-clients", + "description": "${role_view-clients}", + "composite": "true", + "composites": { + "client": { + "realm-management": ["query-clients"] + } + }, + "clientRole": "true", + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "b575be2b-e250-4000-b75e-3038cda8c0dd", + "name": "manage-events", + "description": "${role_manage-events}", + "composite": "false", + "clientRole": "true", + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "e19cd0bf-8da0-457d-b630-454c611bc1ba", + "name": "view-users", + "description": "${role_view-users}", + "composite": "true", + "composites": { + "client": { + "realm-management": ["query-users", "query-groups"] + } + }, + "clientRole": "true", + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "c12145cc-cbdc-4ef3-9774-19b1852811ba", + "name": "query-realms", + "description": "${role_query-realms}", + "composite": "false", + "clientRole": "true", + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "e7e15b84-4971-4c13-be93-315bb36d30e1", + "name": "view-realm", + "description": "${role_view-realm}", + "composite": "false", + "clientRole": "true", + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "e03d2989-a620-4918-85ed-3eabd0373bb4", + "name": "query-groups", + "description": "${role_query-groups}", + "composite": "false", + "clientRole": "true", + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "daf8d347-4b30-41d6-a431-7b3723dd8e6f", + "name": "manage-realm", + "description": "${role_manage-realm}", + "composite": "false", + "clientRole": "true", + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + }, + { + "id": "432cd3eb-4741-46ba-938a-94ff9dece315", + "name": "query-clients", + "description": "${role_query-clients}", + "composite": "false", + "clientRole": "true", + "containerId": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "attributes": {} + } + ], + "security-admin-console": [], + "admin-cli": [], + "account-console": [], + "broker": [ + { + "id": "2e713186-38da-44d7-a5a5-19d91ef2dfca", + "name": "read-token", + "description": "${role_read-token}", + "composite": "false", + "clientRole": "true", + "containerId": "41dd8f26-46c2-471a-859e-01886f972ff9", + "attributes": {} + } + ], + "conversations": [], + "account": [ + { + "id": "63b1a4e1-a594-4571-99c3-7c5c3efd61ce", + "name": "manage-consent", + "description": "${role_manage-consent}", + "composite": "true", + "composites": { + "client": { + "account": ["view-consent"] + } + }, + "clientRole": "true", + "containerId": "06721011-1061-4ca7-944f-be2a20719e20", + "attributes": {} + }, + { + "id": "36ef5fd6-1167-4ba0-9171-c8cb6cfe904b", + "name": "view-groups", + "description": "${role_view-groups}", + "composite": "false", + "clientRole": "true", + "containerId": "06721011-1061-4ca7-944f-be2a20719e20", + "attributes": {} + }, + { + "id": "f984654a-fca5-45d9-bb47-73009eb9bcf0", + "name": "view-profile", + "description": "${role_view-profile}", + "composite": "false", + "clientRole": "true", + "containerId": "06721011-1061-4ca7-944f-be2a20719e20", + "attributes": {} + }, + { + "id": "d54168c5-58a5-4f13-9fa8-6dbbee0e4b73", + "name": "manage-account", + "description": "${role_manage-account}", + "composite": "true", + "composites": { + "client": { + "account": ["manage-account-links"] + } + }, + "clientRole": "true", + "containerId": "06721011-1061-4ca7-944f-be2a20719e20", + "attributes": {} + }, + { + "id": "092b6808-1ee2-44be-9b5d-085ccd6862b4", + "name": "manage-account-links", + "description": "${role_manage-account-links}", + "composite": "false", + "clientRole": "true", + "containerId": "06721011-1061-4ca7-944f-be2a20719e20", + "attributes": {} + }, + { + "id": "ddd57af0-2a5e-4f9d-98e5-ec96c8d852ce", + "name": "view-applications", + "description": "${role_view-applications}", + "composite": "false", + "clientRole": "true", + "containerId": "06721011-1061-4ca7-944f-be2a20719e20", + "attributes": {} + }, + { + "id": "84c7324a-4724-41fe-8bd4-848ce5cebd5b", + "name": "view-consent", + "description": "${role_view-consent}", + "composite": "false", + "clientRole": "true", + "containerId": "06721011-1061-4ca7-944f-be2a20719e20", + "attributes": {} + }, + { + "id": "20d06f75-ea65-4b99-b9ef-2384ffd1de53", + "name": "delete-account", + "description": "${role_delete-account}", + "composite": "false", + "clientRole": "true", + "containerId": "06721011-1061-4ca7-944f-be2a20719e20", + "attributes": {} + } + ] + } + }, + "groups": [], + "defaultRole": { + "id": "1bfe401a-08fc-4d94-80e0-86c4f5195f99", + "name": "default-roles-conversations", + "description": "${role_default-roles}", + "composite": "true", + "clientRole": "false", + "containerId": "ccf4fd40-4286-474d-854a-4714282a8bec" + }, + "requiredCredentials": ["password"], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpPolicyCodeReusable": "false", + "otpSupportedApplications": ["totpAppGoogleName", "totpAppFreeOTPName"], + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": ["ES256"], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": "false", + "webAuthnPolicyAcceptableAaguids": [], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": ["ES256"], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": "false", + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": ["offline_access"] + } + ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": ["manage-account", "view-groups"] + } + ] + }, + "clients": [ + { + "id": "06721011-1061-4ca7-944f-be2a20719e20", + "clientId": "account", + "name": "${client_account}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/conversations/account/", + "surrogateAuthRequired": "false", + "enabled": "true", + "alwaysDisplayInConsole": "false", + "clientAuthenticatorType": "client-secret", + "redirectUris": ["/realms/conversations/account/*"], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": "false", + "consentRequired": "false", + "standardFlowEnabled": "true", + "implicitFlowEnabled": "false", + "directAccessGrantsEnabled": "false", + "serviceAccountsEnabled": "false", + "publicClient": "true", + "frontchannelLogout": "false", + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": "false", + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "987e14a5-caed-40a6-8bac-8c429b74ca48", + "clientId": "account-console", + "name": "${client_account-console}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/conversations/account/", + "surrogateAuthRequired": "false", + "enabled": "true", + "alwaysDisplayInConsole": "false", + "clientAuthenticatorType": "client-secret", + "redirectUris": ["/realms/conversations/account/*"], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": "false", + "consentRequired": "false", + "standardFlowEnabled": "true", + "implicitFlowEnabled": "false", + "directAccessGrantsEnabled": "false", + "serviceAccountsEnabled": "false", + "publicClient": "true", + "frontchannelLogout": "false", + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": "false", + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "4f958126-eaa1-46d5-967a-3a3c2e2d11f7", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": "false", + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "92da37ad-e8a1-41f1-93c6-541dffa7d601", + "clientId": "admin-cli", + "name": "${client_admin-cli}", + "surrogateAuthRequired": "false", + "enabled": "true", + "alwaysDisplayInConsole": "false", + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": "false", + "consentRequired": "false", + "standardFlowEnabled": "false", + "implicitFlowEnabled": "false", + "directAccessGrantsEnabled": "true", + "serviceAccountsEnabled": "false", + "publicClient": "true", + "frontchannelLogout": "false", + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": "false", + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "41dd8f26-46c2-471a-859e-01886f972ff9", + "clientId": "broker", + "name": "${client_broker}", + "surrogateAuthRequired": "false", + "enabled": "true", + "alwaysDisplayInConsole": "false", + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": "true", + "consentRequired": "false", + "standardFlowEnabled": "true", + "implicitFlowEnabled": "false", + "directAccessGrantsEnabled": "false", + "serviceAccountsEnabled": "false", + "publicClient": "false", + "frontchannelLogout": "false", + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": "false", + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "869481d0-5774-4e64-bc30-fedc7c58958f", + "clientId": "conversations", + "name": "", + "description": "", + "rootUrl": "", + "adminUrl": "", + "baseUrl": "", + "surrogateAuthRequired": "false", + "enabled": "true", + "alwaysDisplayInConsole": "false", + "clientAuthenticatorType": "client-secret", + "secret": "ThisIsAnExampleKeyForDevPurposeOnly", + "redirectUris": [ + "http://localhost:8070/*", + "http://localhost:8071/*", + "https://conversations.127.0.0.1.nip.io/*", + "http://localhost:8088/*", + "http://localhost:3000/*" + ], + "webOrigins": [ + "https://conversations.127.0.0.1.nip.io", + "http://localhost:8088", + "http://localhost:8070", + "http://localhost:3000" + ], + "notBefore": 0, + "bearerOnly": "false", + "consentRequired": "false", + "standardFlowEnabled": "true", + "implicitFlowEnabled": "false", + "directAccessGrantsEnabled": "false", + "serviceAccountsEnabled": "false", + "publicClient": "false", + "frontchannelLogout": "true", + "protocol": "openid-connect", + "attributes": { + "access.token.lifespan": "-1", + "client.secret.creation.time": "1707820779", + "user.info.response.signature.alg": "RS256", + "post.logout.redirect.uris": "http://localhost:8070/*##https://conversations.127.0.0.1.nip.io/*##http://localhost:3000/*", + "oauth2.device.authorization.grant.enabled": "false", + "use.jwks.url": "false", + "backchannel.logout.revoke.offline.tokens": "false", + "use.refresh.tokens": "true", + "tls-client-certificate-bound-access-tokens": "false", + "oidc.ciba.grant.enabled": "false", + "backchannel.logout.session.required": "true", + "client_credentials.use_refresh_token": "false", + "acr.loa.map": "{}", + "require.pushed.authorization.requests": "false", + "display.on.consent.screen": "false", + "client.session.idle.timeout": "-1", + "token.response.type.bearer.lower-case": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": "true", + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "0d004a05-7049-452c-83a8-2bae2b5d8015", + "clientId": "realm-management", + "name": "${client_realm-management}", + "surrogateAuthRequired": "false", + "enabled": "true", + "alwaysDisplayInConsole": "false", + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": "true", + "consentRequired": "false", + "standardFlowEnabled": "true", + "implicitFlowEnabled": "false", + "directAccessGrantsEnabled": "false", + "serviceAccountsEnabled": "false", + "publicClient": "false", + "frontchannelLogout": "false", + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": "false", + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "2a4e007a-2fc4-4f43-aace-b93aec9221b4", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/conversations/console/", + "surrogateAuthRequired": "false", + "enabled": "true", + "alwaysDisplayInConsole": "false", + "clientAuthenticatorType": "client-secret", + "redirectUris": ["/admin/conversations/console/*"], + "webOrigins": ["+"], + "notBefore": 0, + "bearerOnly": "false", + "consentRequired": "false", + "standardFlowEnabled": "true", + "implicitFlowEnabled": "false", + "directAccessGrantsEnabled": "false", + "serviceAccountsEnabled": "false", + "publicClient": "true", + "frontchannelLogout": "false", + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": "false", + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "4913be96-5827-46a4-9909-562c2dd5bef6", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": "false", + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "74aeb8e2-a1b6-4897-9eaf-d922becea170", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "consent.screen.text": "${rolesScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "994b8f5e-dfc1-4154-a936-347336e6422a", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": "false", + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "d853f97e-80f8-470e-8447-815b289d9ae3", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": "false", + "config": {} + }, + { + "id": "26a9f3ef-cff0-4dee-9fe9-778cd1d2a771", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": "false", + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + } + ] + }, + { + "id": "af52ccc3-4ecb-49b4-9a67-5d4172f16070", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "efb82630-8835-4de0-944e-ac5ea51eca48", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": "false", + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "2256189a-7970-4244-b496-64cbba3ce582", + "name": "acr", + "description": "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "6d7f8b9e-997e-40f8-bae5-83d2647fbeff", + "name": "acr loa level", + "protocol": "openid-connect", + "protocolMapper": "oidc-acr-mapper", + "consentRequired": "false", + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + } + ] + }, + { + "id": "b83cebb6-f086-48e2-8e5a-9802736342f2", + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "id": "b99113c6-ccfb-43d4-acd1-09dd34cdf5bc", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${addressScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "696211d7-c434-495f-b3a0-a1b88bebfd6e", + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": "false", + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + }, + { + "id": "16845bd9-5626-4484-b4c5-00af52d8ad8b", + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false", + "consent.screen.text": "" + }, + "protocolMappers": [ + { + "id": "5828a7d9-cdc7-456b-a747-16bf83c2f57d", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": "false", + "config": {} + } + ] + }, + { + "id": "ce289e05-eca4-4323-b457-822d39cc6d49", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${profileScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "abe63488-9a39-4e29-a0a8-824db0887b60", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": "false", + "config": { + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "id": "15690cfb-e14c-46e8-8494-22a0365a4b0c", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": "false", + "config": { + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + }, + { + "id": "03cf0e4c-c2a5-4203-88c4-5391d361ba15", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": "false", + "config": { + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "id": "23b1a1da-2ecc-4db7-8d33-4e9233a81e89", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": "false", + "config": { + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "long" + } + }, + { + "id": "26a72777-56eb-4b46-acca-eca8168e29fc", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": "false", + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "id": "4ae1896b-ea82-4604-8f0e-72133fdee05c", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": "false", + "config": { + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "id": "79712bcf-b7f7-4ca3-b97c-418f48fded9b", + "name": "first name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": "false", + "config": { + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "first_name", + "jsonType.label": "String" + } + }, + { + "id": "6397c5e9-95ea-4c31-bd44-a8acf1d18472", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": "false", + "config": { + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "id": "7f741e96-41fe-4021-bbfd-506e7eb94e69", + "name": "last name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": "false", + "config": { + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "last_name", + "jsonType.label": "String" + } + }, + { + "id": "5ca62964-2d04-4e8e-963d-e3b08cf32d7c", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": "false", + "config": { + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "id": "954a5dff-cc19-4dde-b996-787f767db4cc", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": "false", + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "1eba19bf-6fa1-4608-ad2d-d4346580c93d", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": "false", + "config": { + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "id": "e7bdd267-fcce-451f-b3e1-a775cf611dd2", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": "false", + "config": { + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "id": "a9a8918c-af00-48a5-a8b3-a28a83653f71", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": "false", + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "cd725067-b6ba-42f1-a940-97a16a23cb85", + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "a4e1812c-4093-4666-a6b3-03c5d9b5ca9f", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": "false", + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + }, + { + "id": "d6690292-74d1-48ac-855d-2f0f3799829e", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": "false", + "config": { + "multivalued": "true", + "userinfo.token.claim": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "ce8f1215-0462-4e87-8a3b-18488aee0267", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${phoneScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "0ce95430-80aa-4dd6-994b-5a67302ba531", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": "false", + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + }, + { + "id": "8da0d3b1-d609-417e-9adc-1de77549baf9", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": "false", + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + } + ] + }, + { + "id": "f89a9158-7c03-49b0-8a3c-d0b75e2ce1b4", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${emailScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "fb109597-e31e-46d7-84c5-62e5fcf32ac8", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": "false", + "config": { + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + }, + { + "id": "61c135e5-2447-494b-bc70-9612f383be27", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": "false", + "config": { + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + } + ] + } + ], + "defaultDefaultClientScopes": [ + "role_list", + "profile", + "email", + "roles", + "web-origins", + "acr" + ], + "defaultOptionalClientScopes": [ + "offline_access", + "address", + "phone", + "microprofile-jwt" + ], + "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", + "xContentTypeOptions": "nosniff", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection": "1; mode=block", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "smtpServer": {}, + "eventsEnabled": "false", + "eventsListeners": ["jboss-logging"], + "enabledEventTypes": [], + "adminEventsEnabled": "false", + "adminEventsDetailsEnabled": "false", + "identityProviders": [], + "identityProviderMappers": [], + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "74dffa9a-5d4f-4ce3-9708-885212f56861", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "48096073-ceae-4e68-a15b-f1aa390dcce5", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": ["true"] + } + }, + { + "id": "51b0e87c-ee04-4664-a299-f8e49cb7a9ac", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": ["200"] + } + }, + { + "id": "6379b091-2289-4fe7-894c-c03f1bd0e69b", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": ["true"] + } + }, + { + "id": "97ae8320-a439-463b-817e-05bd4a6c39d1", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-role-list-mapper", + "oidc-usermodel-attribute-mapper", + "saml-user-attribute-mapper", + "saml-user-property-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-full-name-mapper", + "oidc-address-mapper", + "oidc-usermodel-property-mapper" + ] + } + }, + { + "id": "49131ffc-4831-4e3e-a466-f9f08aa1bee0", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "e12647d2-e21f-49bc-a8c6-28154c5544d2", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-user-property-mapper", + "saml-user-attribute-mapper", + "oidc-address-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-usermodel-property-mapper", + "oidc-full-name-mapper", + "saml-role-list-mapper", + "oidc-usermodel-attribute-mapper" + ] + } + }, + { + "id": "c9f00ef2-00d9-44bd-9b6c-3b3bf57e44ba", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": ["true"], + "client-uris-must-match": ["true"] + } + } + ], + "org.keycloak.userprofile.UserProfileProvider": [ + { + "id": "96260850-72a5-4b49-b96b-5a33d0b5337d", + "providerId": "declarative-user-profile", + "subComponents": {}, + "config": {} + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "55d93b4d-fe05-46a1-a832-36f380aaddf7", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "priority": ["100"] + } + }, + { + "id": "bee288b4-ecdf-4ec4-8c31-ee330f1e8f95", + "name": "hmac-generated", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "priority": ["100"], + "algorithm": ["HS256"] + } + }, + { + "id": "2aa8f54d-8b4b-4eb7-a05b-89211f544358", + "name": "rsa-enc-generated", + "providerId": "rsa-enc-generated", + "subComponents": {}, + "config": { + "priority": ["100"], + "algorithm": ["RSA-OAEP"] + } + }, + { + "id": "23ad48f4-2275-4a0d-aa0d-1e0691f9c620", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "priority": ["100"] + } + } + ] + }, + "internationalizationEnabled": "false", + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "0c349304-21fd-47ff-8dc6-46efb107b7e9", + "alias": "Account verification options", + "description": "Method with which to verity the existing account", + "providerId": "basic-flow", + "topLevel": "false", + "builtIn": "true", + "authenticationExecutions": [ + { + "authenticator": "idp-email-verification", + "authenticatorFlow": "false", + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + }, + { + "authenticatorFlow": "true", + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": "true", + "flowAlias": "Verify Existing Account by Re-authentication", + "userSetupAllowed": "false" + } + ] + }, + { + "id": "cf1ed416-7274-4804-88bf-4261b0bacdc6", + "alias": "Authentication Options", + "description": "Authentication options.", + "providerId": "basic-flow", + "topLevel": "false", + "builtIn": "true", + "authenticationExecutions": [ + { + "authenticator": "basic-auth", + "authenticatorFlow": "false", + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + }, + { + "authenticator": "basic-auth-otp", + "authenticatorFlow": "false", + "requirement": "DISABLED", + "priority": 20, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": "false", + "requirement": "DISABLED", + "priority": 30, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + } + ] + }, + { + "id": "d949f1f1-4622-49ec-b74a-4b8a58c653d2", + "alias": "Browser - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": "false", + "builtIn": "true", + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": "false", + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": "false", + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + } + ] + }, + { + "id": "3deb6d9d-2064-410c-af99-b1601cd9b1c4", + "alias": "Direct Grant - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": "false", + "builtIn": "true", + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": "false", + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + }, + { + "authenticator": "direct-grant-validate-otp", + "authenticatorFlow": "false", + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + } + ] + }, + { + "id": "f777c4be-f7d1-453e-a9d7-a2a235b7975b", + "alias": "First broker login - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": "false", + "builtIn": "true", + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": "false", + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": "false", + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + } + ] + }, + { + "id": "1bc12f49-e2ef-42bd-959a-0983e1cd4d65", + "alias": "Handle Existing Account", + "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId": "basic-flow", + "topLevel": "false", + "builtIn": "true", + "authenticationExecutions": [ + { + "authenticator": "idp-confirm-link", + "authenticatorFlow": "false", + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + }, + { + "authenticatorFlow": "true", + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": "true", + "flowAlias": "Account verification options", + "userSetupAllowed": "false" + } + ] + }, + { + "id": "324cdcf5-8f31-4768-9db9-63208f182b39", + "alias": "Reset - Conditional OTP", + "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId": "basic-flow", + "topLevel": "false", + "builtIn": "true", + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": "false", + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + }, + { + "authenticator": "reset-otp", + "authenticatorFlow": "false", + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + } + ] + }, + { + "id": "23d17138-8ebd-4195-91d3-614094f62070", + "alias": "User creation or linking", + "description": "Flow for the existing/non-existing user alternatives", + "providerId": "basic-flow", + "topLevel": "false", + "builtIn": "true", + "authenticationExecutions": [ + { + "authenticatorConfig": "create unique user config", + "authenticator": "idp-create-user-if-unique", + "authenticatorFlow": "false", + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + }, + { + "authenticatorFlow": "true", + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": "true", + "flowAlias": "Handle Existing Account", + "userSetupAllowed": "false" + } + ] + }, + { + "id": "61fec72a-bfd2-42e8-95c1-fa0b76c1cd2b", + "alias": "Verify Existing Account by Re-authentication", + "description": "Reauthentication of existing account", + "providerId": "basic-flow", + "topLevel": "false", + "builtIn": "true", + "authenticationExecutions": [ + { + "authenticator": "idp-username-password-form", + "authenticatorFlow": "false", + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + }, + { + "authenticatorFlow": "true", + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": "true", + "flowAlias": "First broker login - Conditional OTP", + "userSetupAllowed": "false" + } + ] + }, + { + "id": "dc00b9a8-fc37-4591-a1ea-07c7f884d394", + "alias": "browser", + "description": "browser based authentication", + "providerId": "basic-flow", + "topLevel": "true", + "builtIn": "true", + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "authenticatorFlow": "false", + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": "false", + "requirement": "DISABLED", + "priority": 20, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + }, + { + "authenticator": "identity-provider-redirector", + "authenticatorFlow": "false", + "requirement": "ALTERNATIVE", + "priority": 25, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + }, + { + "authenticatorFlow": "true", + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": "true", + "flowAlias": "forms", + "userSetupAllowed": "false" + } + ] + }, + { + "id": "4f27245a-49b8-4870-a5e2-f0ea624a792c", + "alias": "clients", + "description": "Base authentication for clients", + "providerId": "client-flow", + "topLevel": "true", + "builtIn": "true", + "authenticationExecutions": [ + { + "authenticator": "client-secret", + "authenticatorFlow": "false", + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + }, + { + "authenticator": "client-jwt", + "authenticatorFlow": "false", + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + }, + { + "authenticator": "client-secret-jwt", + "authenticatorFlow": "false", + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + }, + { + "authenticator": "client-x509", + "authenticatorFlow": "false", + "requirement": "ALTERNATIVE", + "priority": 40, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + } + ] + }, + { + "id": "5b2c66e1-7bbf-4707-9db8-244269b68164", + "alias": "direct grant", + "description": "OpenID Connect Resource Owner Grant", + "providerId": "basic-flow", + "topLevel": "true", + "builtIn": "true", + "authenticationExecutions": [ + { + "authenticator": "direct-grant-validate-username", + "authenticatorFlow": "false", + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + }, + { + "authenticator": "direct-grant-validate-password", + "authenticatorFlow": "false", + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + }, + { + "authenticatorFlow": "true", + "requirement": "CONDITIONAL", + "priority": 30, + "autheticatorFlow": "true", + "flowAlias": "Direct Grant - Conditional OTP", + "userSetupAllowed": "false" + } + ] + }, + { + "id": "4bcddec4-4260-4f4f-a757-3aff9b1d30f3", + "alias": "docker auth", + "description": "Used by Docker clients to authenticate against the IDP", + "providerId": "basic-flow", + "topLevel": "true", + "builtIn": "true", + "authenticationExecutions": [ + { + "authenticator": "docker-http-basic-authenticator", + "authenticatorFlow": "false", + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + } + ] + }, + { + "id": "04a94e38-b7fb-48f6-8d63-5640f835c619", + "alias": "first broker login", + "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId": "basic-flow", + "topLevel": "true", + "builtIn": "true", + "authenticationExecutions": [ + { + "authenticatorConfig": "review profile config", + "authenticator": "idp-review-profile", + "authenticatorFlow": "false", + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + }, + { + "authenticatorFlow": "true", + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": "true", + "flowAlias": "User creation or linking", + "userSetupAllowed": "false" + } + ] + }, + { + "id": "bfcf5112-96ac-485a-8663-b02ad41af919", + "alias": "forms", + "description": "Username, password, otp and other auth forms.", + "providerId": "basic-flow", + "topLevel": "false", + "builtIn": "true", + "authenticationExecutions": [ + { + "authenticator": "auth-username-password-form", + "authenticatorFlow": "false", + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + }, + { + "authenticatorFlow": "true", + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": "true", + "flowAlias": "Browser - Conditional OTP", + "userSetupAllowed": "false" + } + ] + }, + { + "id": "e262d10d-ad0d-4d18-bc05-3a44f7d21736", + "alias": "http challenge", + "description": "An authentication flow based on challenge-response HTTP Authentication Schemes", + "providerId": "basic-flow", + "topLevel": "true", + "builtIn": "true", + "authenticationExecutions": [ + { + "authenticator": "no-cookie-redirect", + "authenticatorFlow": "false", + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + }, + { + "authenticatorFlow": "true", + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": "true", + "flowAlias": "Authentication Options", + "userSetupAllowed": "false" + } + ] + }, + { + "id": "b671c4b3-22b6-4aac-a1d1-464a2101767c", + "alias": "registration", + "description": "registration flow", + "providerId": "basic-flow", + "topLevel": "true", + "builtIn": "true", + "authenticationExecutions": [ + { + "authenticator": "registration-page-form", + "authenticatorFlow": "true", + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": "true", + "flowAlias": "registration form", + "userSetupAllowed": "false" + } + ] + }, + { + "id": "f570e064-0e62-4eae-8087-8b06751b8f33", + "alias": "registration form", + "description": "registration form", + "providerId": "form-flow", + "topLevel": "false", + "builtIn": "true", + "authenticationExecutions": [ + { + "authenticator": "registration-user-creation", + "authenticatorFlow": "false", + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + }, + { + "authenticator": "registration-profile-action", + "authenticatorFlow": "false", + "requirement": "REQUIRED", + "priority": 40, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + }, + { + "authenticator": "registration-password-action", + "authenticatorFlow": "false", + "requirement": "REQUIRED", + "priority": 50, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + }, + { + "authenticator": "registration-recaptcha-action", + "authenticatorFlow": "false", + "requirement": "DISABLED", + "priority": 60, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + } + ] + }, + { + "id": "07124099-1d10-4148-ac06-4b0b700908da", + "alias": "reset credentials", + "description": "Reset credentials for a user if they forgot their password or something", + "providerId": "basic-flow", + "topLevel": "true", + "builtIn": "true", + "authenticationExecutions": [ + { + "authenticator": "reset-credentials-choose-user", + "authenticatorFlow": "false", + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + }, + { + "authenticator": "reset-credential-email", + "authenticatorFlow": "false", + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + }, + { + "authenticator": "reset-password", + "authenticatorFlow": "false", + "requirement": "REQUIRED", + "priority": 30, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + }, + { + "authenticatorFlow": "true", + "requirement": "CONDITIONAL", + "priority": 40, + "autheticatorFlow": "true", + "flowAlias": "Reset - Conditional OTP", + "userSetupAllowed": "false" + } + ] + }, + { + "id": "0a5fa089-f987-4903-9170-36565edda152", + "alias": "saml ecp", + "description": "SAML ECP Profile Authentication Flow", + "providerId": "basic-flow", + "topLevel": "true", + "builtIn": "true", + "authenticationExecutions": [ + { + "authenticator": "http-basic-authenticator", + "authenticatorFlow": "false", + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": "false", + "userSetupAllowed": "false" + } + ] + } + ], + "authenticatorConfig": [ + { + "id": "d2818365-2189-4003-9817-0ad5368e37f3", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "72508559-0176-4eee-a77e-0795d652be12", + "alias": "review profile config", + "config": { + "update.profile.on.first.login": "missing" + } + } + ], + "requiredActions": [ + { + "alias": "CONFIGURE_TOTP", + "name": "Configure OTP", + "providerId": "CONFIGURE_TOTP", + "enabled": "true", + "defaultAction": "false", + "priority": 10, + "config": {} + }, + { + "alias": "terms_and_conditions", + "name": "Terms and Conditions", + "providerId": "terms_and_conditions", + "enabled": "false", + "defaultAction": "false", + "priority": 20, + "config": {} + }, + { + "alias": "UPDATE_PASSWORD", + "name": "Update Password", + "providerId": "UPDATE_PASSWORD", + "enabled": "true", + "defaultAction": "false", + "priority": 30, + "config": {} + }, + { + "alias": "UPDATE_PROFILE", + "name": "Update Profile", + "providerId": "UPDATE_PROFILE", + "enabled": "true", + "defaultAction": "false", + "priority": 40, + "config": {} + }, + { + "alias": "VERIFY_EMAIL", + "name": "Verify Email", + "providerId": "VERIFY_EMAIL", + "enabled": "true", + "defaultAction": "false", + "priority": 50, + "config": {} + }, + { + "alias": "delete_account", + "name": "Delete Account", + "providerId": "delete_account", + "enabled": "false", + "defaultAction": "false", + "priority": 60, + "config": {} + }, + { + "alias": "CONFIGURE_RECOVERY_AUTHN_CODES", + "name": "Recovery Authentication Codes", + "providerId": "CONFIGURE_RECOVERY_AUTHN_CODES", + "enabled": "true", + "defaultAction": "false", + "priority": 70, + "config": {} + }, + { + "alias": "UPDATE_EMAIL", + "name": "Update Email", + "providerId": "UPDATE_EMAIL", + "enabled": "true", + "defaultAction": "false", + "priority": 70, + "config": {} + }, + { + "alias": "webauthn-register", + "name": "Webauthn Register", + "providerId": "webauthn-register", + "enabled": "true", + "defaultAction": "false", + "priority": 70, + "config": {} + }, + { + "alias": "webauthn-register-passwordless", + "name": "Webauthn Register Passwordless", + "providerId": "webauthn-register-passwordless", + "enabled": "true", + "defaultAction": "false", + "priority": 80, + "config": {} + }, + { + "alias": "update_user_locale", + "name": "Update User Locale", + "providerId": "update_user_locale", + "enabled": "true", + "defaultAction": "false", + "priority": 1000, + "config": {} + } + ], + "browserFlow": "browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "dockerAuthenticationFlow": "docker auth", + "attributes": { + "cibaBackchannelTokenDeliveryMode": "poll", + "cibaExpiresIn": "120", + "cibaAuthRequestedUserHint": "login_hint", + "oauth2DeviceCodeLifespan": "600", + "oauth2DevicePollingInterval": "5", + "clientOfflineSessionMaxLifespan": "0", + "clientSessionIdleTimeout": "0", + "parRequestUriLifespan": "60", + "clientSessionMaxLifespan": "0", + "clientOfflineSessionIdleTimeout": "0", + "cibaInterval": "5", + "realmReusableOtpCode": "false" + }, + "keycloakVersion": "20.0.1", + "userManagedAccessAllowed": "false", + "clientProfiles": { + "profiles": [] + }, + "clientPolicies": { + "policies": [] + } + } + + diff --git a/docs/examples/minio.values.yaml b/docs/examples/minio.values.yaml new file mode 100644 index 0000000..26f6032 --- /dev/null +++ b/docs/examples/minio.values.yaml @@ -0,0 +1,8 @@ +auth: + rootUser: root + rootPassword: password +provisioning: + enabled: true + buckets: + - name: conversations-media-storage + versioning: true diff --git a/docs/examples/postgresql.values.yaml b/docs/examples/postgresql.values.yaml new file mode 100644 index 0000000..28fb101 --- /dev/null +++ b/docs/examples/postgresql.values.yaml @@ -0,0 +1,7 @@ +auth: + username: dinum + password: pass + database: conversations +tls: + enabled: true + autoGenerated: true diff --git a/docs/examples/redis.values.yaml b/docs/examples/redis.values.yaml new file mode 100644 index 0000000..9e8a5bf --- /dev/null +++ b/docs/examples/redis.values.yaml @@ -0,0 +1,4 @@ +auth: + password: pass +architecture: standalone + diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 0000000..679df16 --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,228 @@ +# Installation on a k8s cluster + +This document is a step-by-step guide that describes how to install Conversations on a k8s cluster without AI features. It's a teaching document to learn how it works. It needs to be adapted for a production environment. + +## Prerequisites + +- k8s cluster with an nginx-ingress controller +- an OIDC provider (if you don't have one, we provide an example) +- a PostgreSQL server (if you don't have one, we provide an example) +- a Memcached server (if you don't have one, we provide an example) +- a S3 bucket (if you don't have one, we provide an example) + +### Test cluster + +If you do not have a test cluster, you can install everything on a local Kind cluster. In this case, the simplest way is to use our script **bin/start-kind.sh**. + +To be able to use the script, you need to install: + +- Docker (https://docs.docker.com/desktop/) +- Kind (https://kind.sigs.k8s.io/docs/user/quick-start/#installation) +- Mkcert (https://github.com/FiloSottile/mkcert#installation) +- Helm (https://helm.sh/docs/intro/quickstart/#install-helm) + +``` +./bin/start-kind.sh + % Total % Received % Xferd Average Speed Time Time Time Current + Dload Upload Total Spent Left Speed +100 4700 100 4700 0 0 92867 0 --:--:-- --:--:-- --:--:-- 94000 +0. Create ca +The local CA is already installed in the system trust store! ๐Ÿ‘ +The local CA is already installed in the Firefox and/or Chrome/Chromium trust store! ๐Ÿ‘ + + +Created a new certificate valid for the following names ๐Ÿ“œ + - "127.0.0.1.nip.io" + - "*.127.0.0.1.nip.io" + +Reminder: X.509 wildcards only go one level deep, so this won't match a.b.127.0.0.1.nip.io โ„น๏ธ + +The certificate is at "./127.0.0.1.nip.io+1.pem" and the key at "./127.0.0.1.nip.io+1-key.pem" โœ… + +It will expire on 24 March 2027 ๐Ÿ—“ + +1. Create registry container unless it already exists +2. Create kind cluster with containerd registry config dir enabled +Creating cluster "suite" ... + โœ“ Ensuring node image (kindest/node:v1.27.3) ๐Ÿ–ผ + โœ“ Preparing nodes ๐Ÿ“ฆ + โœ“ Writing configuration ๐Ÿ“œ + โœ“ Starting control-plane ๐Ÿ•น๏ธ + โœ“ Installing CNI ๐Ÿ”Œ + โœ“ Installing StorageClass ๐Ÿ’พ +Set kubectl context to "kind-suite" +You can now use your cluster with: + +kubectl cluster-info --context kind-suite + +Thanks for using kind! ๐Ÿ˜Š +3. Add the registry config to the nodes +4. Connect the registry to the cluster network if not already connected +5. Document the local registry +configmap/local-registry-hosting created +Warning: resource configmaps/coredns is missing the kubectl.kubernetes.io/last-applied-configuration annotation which is required by kubectl apply. kubectl apply should only be used on resources created declaratively by either kubectl create --save-config or kubectl apply. The missing annotation will be patched automatically. +configmap/coredns configured +deployment.apps/coredns restarted +6. Install ingress-nginx +namespace/ingress-nginx created +serviceaccount/ingress-nginx created +serviceaccount/ingress-nginx-admission created +role.rbac.authorization.k8s.io/ingress-nginx created +role.rbac.authorization.k8s.io/ingress-nginx-admission created +clusterrole.rbac.authorization.k8s.io/ingress-nginx created +clusterrole.rbac.authorization.k8s.io/ingress-nginx-admission created +rolebinding.rbac.authorization.k8s.io/ingress-nginx created +rolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created +clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created +clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created +configmap/ingress-nginx-controller created +service/ingress-nginx-controller created +service/ingress-nginx-controller-admission created +deployment.apps/ingress-nginx-controller created +job.batch/ingress-nginx-admission-create created +job.batch/ingress-nginx-admission-patch created +ingressclass.networking.k8s.io/nginx created +validatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission created +secret/mkcert created +deployment.apps/ingress-nginx-controller patched +7. Setup namespace +namespace/conversations created +Context "kind-suite" modified. +secret/mkcert created +$ kubectl -n ingress-nginx get po +NAME READY STATUS RESTARTS AGE +ingress-nginx-admission-create-t55ph 0/1 Completed 0 2m56s +ingress-nginx-admission-patch-94dvt 0/1 Completed 1 2m56s +ingress-nginx-controller-57c548c4cd-2rx47 1/1 Running 0 2m56s +``` + +When your k8s cluster is ready (the ingress nginx controller is up), you can start the deployment. This cluster is special because it uses the `*.127.0.0.1.nip.io` domain and mkcert certificates to have full HTTPS support and easy domain name management. + +Please remember that `*.127.0.0.1.nip.io` will always resolve to `127.0.0.1`, except in the k8s cluster where we configure CoreDNS to answer with the ingress-nginx service IP. + +## Preparation + +### What do you use to authenticate your users? + +Conversations uses OIDC, so if you already have an OIDC provider, obtain the necessary information to use it. In the next step, we will see how to configure Django (and thus Conversations) to use it. If you do not have a provider, we will show you how to deploy a local Keycloak instance (this is not a production deployment, just a demo). + +``` +$ kubectl create namespace conversations +$ kubectl config set-context --current --namespace=conversations +$ helm install keycloak oci://registry-1.docker.io/bitnamicharts/keycloak -f examples/keycloak.values.yaml +$ #wait until +$ kubectl get po +NAME READY STATUS RESTARTS AGE +keycloak-0 1/1 Running 0 6m48s +keycloak-postgresql-0 1/1 Running 0 6m48s +``` + +From here the important information you will need are: + +```yaml +OIDC_OP_JWKS_ENDPOINT: https://keycloak.127.0.0.1.nip.io/realms/conversations/protocol/openid-connect/certs +OIDC_OP_AUTHORIZATION_ENDPOINT: https://keycloak.127.0.0.1.nip.io/realms/conversations/protocol/openid-connect/auth +OIDC_OP_TOKEN_ENDPOINT: https://keycloak.127.0.0.1.nip.io/realms/conversations/protocol/openid-connect/token +OIDC_OP_USER_ENDPOINT: https://keycloak.127.0.0.1.nip.io/realms/conversations/protocol/openid-connect/userinfo +OIDC_OP_LOGOUT_ENDPOINT: https://keycloak.127.0.0.1.nip.io/realms/conversations/protocol/openid-connect/session/end +OIDC_RP_CLIENT_ID: conversations +OIDC_RP_CLIENT_SECRET: ThisIsAnExampleKeyForDevPurposeOnly +OIDC_RP_SIGN_ALGO: RS256 +OIDC_RP_SCOPES: "openid email" +``` + +You can find these values in **examples/keycloak.values.yaml** + +### Find redis server connection values + +Conversations needs a redis so we start by deploying one: + +``` +$ helm install redis oci://registry-1.docker.io/bitnamicharts/redis -f examples/redis.values.yaml +$ kubectl get po +NAME READY STATUS RESTARTS AGE +keycloak-0 1/1 Running 0 26m +keycloak-postgresql-0 1/1 Running 0 26m +redis-master-0 1/1 Running 0 35s +``` + +### Find postgresql connection values + +Conversations uses a postgresql database as backend, so if you have a provider, obtain the necessary information to use it. If you don't, you can install a postgresql testing environment as follow: + +``` +$ helm install postgresql oci://registry-1.docker.io/bitnamicharts/postgresql -f examples/postgresql.values.yaml +$ kubectl get po +NAME READY STATUS RESTARTS AGE +keycloak-0 1/1 Running 0 28m +keycloak-postgresql-0 1/1 Running 0 28m +postgresql-0 1/1 Running 0 14m +redis-master-0 1/1 Running 0 42s +``` + +From here the important information you will need are: + +```yaml +DB_HOST: postgres-postgresql +DB_NAME: conversations +DB_USER: dinum +DB_PASSWORD: pass +DB_PORT: 5432 +POSTGRES_DB: conversations +POSTGRES_USER: dinum +POSTGRES_PASSWORD: pass +``` + +### Find s3 bucket connection values + +Conversations uses an s3 bucket to store documents, so if you have a provider obtain the necessary information to use it. If you don't, you can install a local minio testing environment as follow: + +``` +$ helm install minio oci://registry-1.docker.io/bitnamicharts/minio -f examples/minio.values.yaml +$ kubectl get po +NAME READY STATUS RESTARTS AGE +keycloak-0 1/1 Running 0 38m +keycloak-postgresql-0 1/1 Running 0 38m +minio-84f5c66895-bbhsk 1/1 Running 0 42s +minio-provisioning-2b5sq 0/1 Completed 0 42s +postgresql-0 1/1 Running 0 24m +redis-master-0 1/1 Running 0 10m +``` + +## Deployment + +Now you are ready to deploy Conversations without AI. AI requires more dependencies (OpenAI API). To deploy Conversations you need to provide all previous information to the helm chart. + +``` +$ helm repo add conversations https://suitenumerique.github.io/conversations/ +$ helm repo update +$ helm install conversations conversations/conversations -f examples/conversations.values.yaml +$ kubectl get po +NAME READY STATUS RESTARTS AGE +conversations-conversations-backend-96558758d-xtkbp 0/1 Running 0 79s +conversations-conversations-backend-createsuperuser-r7ltc 0/1 Completed 0 79s +conversations-conversations-backend-migrate-c949s 0/1 Completed 0 79s +conversations-conversations-frontend-6749f644f7-p5s42 1/1 Running 0 79s +keycloak-0 1/1 Running 0 48m +keycloak-postgresql-0 1/1 Running 0 48m +minio-84f5c66895-bbhsk 1/1 Running 0 10m +minio-provisioning-2b5sq 0/1 Completed 0 10m +postgresql-0 1/1 Running 0 34m +redis-master-0 1/1 Running 0 20m +``` + +## Test your deployment + +In order to test your deployment you have to log into your instance. If you exclusively use our examples you can do: + +``` +$ kubectl get ingress +NAME CLASS HOSTS ADDRESS PORTS AGE +conversations-conversations conversations.127.0.0.1.nip.io localhost 80, 443 114s +conversations-conversations-admin conversations.127.0.0.1.nip.io localhost 80, 443 114s +conversations-conversations-media conversations.127.0.0.1.nip.io localhost 80, 443 114s +conversations-conversations-ws conversations.127.0.0.1.nip.io localhost 80, 443 114s +keycloak keycloak.127.0.0.1.nip.io localhost 80 49m +``` + +You can use Conversations at https://conversations.127.0.0.1.nip.io. The provisionning user in keycloak is conversations/conversations. diff --git a/docs/release.md b/docs/release.md new file mode 100644 index 0000000..9c8d295 --- /dev/null +++ b/docs/release.md @@ -0,0 +1,66 @@ +# Releasing a new version + +Whenever we are cooking a new release (e.g. `4.18.1`) we should follow a standard procedure described below: + +1. Create a new branch named: `release/4.18.1`. + 2. Bump the release number for backend project, frontend projects, and Helm files: + + - for backend, update the version number by hand in `pyproject.toml`, + - for each projects (`src/frontend`, `src/frontend/apps/*`, `src/frontend/packages/*`, `src/mail`), run `yarn version --new-version --no-git-tag-version 4.18.1` in their directory. This will update their `package.json` for you, + - for Helm, update Docker image tag in files located at `src/helm/env.d` for both `preprod` and `production` environments: + + ```yaml + image: + repository: lasuite/conversations-backend + pullPolicy: Always + tag: "v4.18.1" # Replace with your new version number, without forgetting the "v" prefix + + ... + + frontend: + image: + repository: lasuite/conversations-frontend + pullPolicy: Always + tag: "v4.18.1" + ``` + + The new images don't exist _yet_: they will be created automatically later in the process. + +3. Update the project's `Changelog` following the [keepachangelog](https://keepachangelog.com/en/0.3.0/) recommendations + +4. Commit your changes with the following format: the ๐Ÿ”– release emoji, the type of release (patch/minor/patch) and the release version: + + ```text + ๐Ÿ”–(minor) bump release to 4.18.0 + ``` + +5. Open a pull request, wait for an approval from your peers and merge it. +6. Checkout and pull changes from the `main` branch to ensure you have the latest updates. +7. Tag and push your commit: + + ```bash + git tag v4.18.1 && git push origin tag v4.18.1 + ``` + + Doing this triggers the CI and tells it to build the new Docker image versions that you targeted earlier in the Helm files. + +8. Ensure the new [backend](https://hub.docker.com/r/lasuite/conversations-frontend/tags) and [frontend](https://hub.docker.com/r/lasuite/conversations-frontend/tags) image tags are on Docker Hub. +9. The release is now done! + +# Deploying + +> [!TIP] +> The `staging` platform is deployed automatically with every update of the `main` branch. + +Making a new release doesn't publish it automatically in production. + +Deployment is done by ArgoCD. ArgoCD checks for the `production` tag and automatically deploys the production platform with the targeted commit. + +To publish, we mark the commit we want with the `production` tag. ArgoCD is then notified that the tag has changed. It then deploys the Docker image tags specified in the Helm files of the targeted commit. + +To publish the release you just made: + +```bash +git tag --force production v4.18.1 +git push --force origin production +``` \ No newline at end of file diff --git a/docs/system-requirements.md b/docs/system-requirements.md new file mode 100644 index 0000000..9246c37 --- /dev/null +++ b/docs/system-requirements.md @@ -0,0 +1,109 @@ +# La Suite Docs โ€“ System & Requirements (2025-06) + +## 1. Quick-Reference Matrix (single VM / laptop) + +| Scenario | RAM | vCPU | SSD | Notes | +| ------------------------- | ----- | ---- | ------- | ------------------------- | +| **Solo dev** | 8 GB | 4 | 15 GB | Hot-reload + one IDE | +| **Team QA** | 16 GB | 6 | 30 GB | Runs integration tests | +| **Prod โ‰ค 100 live users** | 32 GB | 8 + | 50 GB + | Scale linearly above this | + +Memory is the first bottleneck; CPU matters only when Celery or the Next.js build is saturated. + +> **Note:** Memory consumption varies by operating system. Windows tends to be more memory-hungry than Linux, so consider adding 10-20% extra RAM when running on Windows compared to Linux-based systems. + +## 2. Development Environment Memory Requirements + +| Service | Typical use | Rationale / source | +| ------------------------ | ----------------------------- | --------------------------------------------------------------------------------------- | +| PostgreSQL | **1 โ€“ 2 GB** | `shared_buffers` starting point โ‰ˆ 25% RAM ([postgresql.org][1]) | +| Keycloak | **โ‰ˆ 1.3 GB** | 70% of limit for heap + ~300 MB non-heap ([keycloak.org][2]) | +| Redis | **โ‰ค 256 MB** | Empty instance โ‰ˆ 3 MB; budget 256 MB to allow small datasets ([stackoverflow.com][3]) | +| MinIO | **2 GB (dev) / 32 GB (prod)**| Pre-allocates 1โ€“2 GiB; docs recommend 32 GB per host for โ‰ค 100 Ti storage ([min.io][4]) | +| Django API (+ Celery) | **0.8 โ€“ 1.5 GB** | Empirical in-house metrics | +| Next.js frontend | **0.5 โ€“ 1 GB** | Dev build chain | +| Y-Provider (y-websocket) | **< 200 MB** | Large 40 MB YDoc called โ€œbigโ€ in community thread ([discuss.yjs.dev][5]) | +| Nginx | **< 100 MB** | Static reverse-proxy footprint | + +[1]: https://www.postgresql.org/docs/9.1/runtime-config-resource.html "PostgreSQL: Documentation: 9.1: Resource Consumption" +[2]: https://www.keycloak.org/high-availability/concepts-memory-and-cpu-sizing "Concepts for sizing CPU and memory resources - Keycloak" +[3]: https://stackoverflow.com/questions/45233052/memory-footprint-for-redis-empty-instance "Memory footprint for Redis empty instance - Stack Overflow" +[4]: https://min.io/docs/minio/kubernetes/upstream/operations/checklists/hardware.html "Hardware Checklist โ€” MinIO Object Storage for Kubernetes" +[5]: https://discuss.yjs.dev/t/understanding-memory-requirements-for-production-usage/198 "Understanding memory requirements for production usage - Yjs Community" + +> **Rule of thumb:** add 2 GB for OS/overhead, then sum only the rows you actually run. + +## 3. Production Environment Memory Requirements + +Production deployments differ significantly from development environments. The table below shows typical memory usage for production services: + +| Service | Typical use | Rationale / notes | +| ------------------------ | ----------------------------- | --------------------------------------------------------------------------------------- | +| PostgreSQL | **2 โ€“ 8 GB** | Higher `shared_buffers` and connection pooling for concurrent users | +| OIDC Provider (optional) | **Variable** | Any OIDC-compatible provider (Keycloak, Auth0, Azure AD, etc.) - external or self-hosted | +| Redis | **256 MB โ€“ 2 GB** | Session storage and caching; scales with active user sessions | +| Object Storage (optional)| **External or self-hosted** | Can use AWS S3, Azure Blob, Google Cloud Storage, or self-hosted MinIO | +| Django API (+ Celery) | **1 โ€“ 3 GB** | Production workloads with background tasks and higher concurrency | +| Static Files (Nginx) | **< 200 MB** | Serves Next.js build output and static assets; no development overhead | +| Y-Provider (y-websocket) | **200 MB โ€“ 1 GB** | Scales with concurrent document editing sessions | +| Nginx (Load Balancer) | **< 200 MB** | Reverse proxy, SSL termination, static file serving | + +### Production Architecture Notes + +- **Frontend**: Uses pre-built Next.js static assets served by Nginx (no Node.js runtime needed) +- **Authentication**: Any OIDC-compatible provider can be used instead of self-hosted Keycloak +- **Object Storage**: External services (S3, Azure Blob) or self-hosted solutions (MinIO) are both viable +- **Database**: Consider PostgreSQL clustering or managed database services for high availability +- **Scaling**: Horizontal scaling is recommended for Django API and Y-Provider services + +### Minimal Production Setup (Core Services Only) + +| Service | Memory | Notes | +|----------------------------------|------------|----------------------------------------| +| PostgreSQL | **2 GB** | Core database | +| Django API (+ Celery) | **1.5 GB** | Backend services | +| Nginx | **100 MB** | Static files + reverse proxy | +| Redis | **256 MB** | Session storage | +| **Total (without auth/storage)** | **โ‰ˆ 4 GB** | External OIDC + object storage assumed | + +## 4. Recommended Software Versions + +| Tool | Minimum | +| ----------------------- | ------- | +| Docker Engine / Desktop | 24.0 | +| Docker Compose | v2 | +| Git | 2.40 | +| **Node.js** | 22+ | +| **Python** | 3.13+ | +| GNU Make | 4.4 | +| Kind | 0.22 | +| Helm | 3.14 | +| kubectl | 1.29 | +| mkcert | 1.4 | + + +## 5. Ports (dev defaults) + +| Port | Service | +| --------- |-----------------------| +| 3000 | Next.js | +| 8071 | Django | +| 4444 | Y-Provider | +| 8080 | Keycloak | +| 8083 | Nginx proxy | +| 9000/9001 | MinIO | +| 15432 | PostgreSQL (main) | +| 5433 | PostgreSQL (Keycloak) | +| 1081 | Maildev | + +## 6. Sizing Guidelines + +**RAM** โ€“ start at 8 GB dev / 16 GB staging / 32 GB prod. Postgres and Keycloak are the first to OOM; scale them first. + +> **OS considerations:** Windows systems typically require 10-20% more RAM than Linux due to higher OS overhead. Docker Desktop on Windows also uses additional memory compared to native Linux Docker. + +**CPU** โ€“ budget one vCPU per busy container until Celery or Next.js builds saturate. + +**Disk** โ€“ SSD; add 10 GB extra for the Docker layer cache. + +**MinIO** โ€“ for demos, mount a local folder instead of running MinIO to save 2 GB+ of RAM. \ No newline at end of file diff --git a/docs/theming.md b/docs/theming.md new file mode 100644 index 0000000..c9f3e66 --- /dev/null +++ b/docs/theming.md @@ -0,0 +1,64 @@ +# Runtime Theming ๐ŸŽจ + +### How to Use + +To use this feature, simply set the `FRONTEND_CSS_URL` environment variable to the URL of your custom CSS file. For example: + +```javascript +FRONTEND_CSS_URL=http://anything/custom-style.css +``` + +Once you've set this variable, our application will load your custom CSS file and apply the styles to our frontend application. + +### Benefits + +This feature provides several benefits, including: + +* **Easy customization** ๐Ÿ”„: With this feature, you can easily customize the look and feel of our application without requiring any code changes. +* **Flexibility** ๐ŸŒˆ: You can use any CSS styles you like to create a custom theme that meets your needs. +* **Runtime theming** โฑ๏ธ: This feature allows you to change the theme of our application at runtime, without requiring a restart or recompilation. + +### Example Use Case + +Let's say you want to change the background color of our application to a custom color. You can create a custom CSS file with the following contents: + +```css +body { + background-color: #3498db; +} +``` + +Then, set the `FRONTEND_CSS_URL` environment variable to the URL of your custom CSS file. Once you've done this, our application will load your custom CSS file and apply the styles, changing the background color to the custom color you specified. + +---- + +# **Footer Configuration** ๐Ÿ“ + +The footer is configurable from the theme customization file. + +### Settings ๐Ÿ”ง + +```shellscript +THEME_CUSTOMIZATION_FILE_PATH= +``` + +### Example of JSON + +The json must follow some rules: https://github.com/suitenumerique/conversations/blob/main/src/helm/env.d/dev/configuration/theme/demo.json + +`footer.default` is the fallback if the language is not supported. + + +# **Custom Translations** ๐Ÿ“ + +The translations can be partially overridden from the theme customization file. + +### Settings ๐Ÿ”ง + +```shellscript +THEME_CUSTOMIZATION_FILE_PATH= +``` + +### Example of JSON + +The json must follow some rules: https://github.com/suitenumerique/conversations/blob/main/src/helm/env.d/dev/configuration/theme/demo.json \ No newline at end of file diff --git a/docs/troubleshoot.md b/docs/troubleshoot.md new file mode 100644 index 0000000..0416306 --- /dev/null +++ b/docs/troubleshoot.md @@ -0,0 +1,192 @@ +# Troubleshooting Guide + +## Line Ending Issues on Windows (LF/CRLF) + +### Problem Description + +This project uses **LF (Line Feed: `\n`) line endings** exclusively. Windows users may encounter issues because: + +- **Windows** defaults to CRLF (Carriage Return + Line Feed: `\r\n`) for line endings +- **This project** uses LF line endings for consistency across all platforms +- **Git** may automatically convert line endings, causing conflicts or build failures + +### Common Symptoms + +- Git shows files as modified even when no changes were made +- Error messages like "warning: LF will be replaced by CRLF" +- Build failures or linting errors due to line ending mismatches + +### Solutions for Windows Users + +#### Configure Git to Preserve LF (Recommended) + +Configure Git to NOT convert line endings and preserve LF: + +```bash +git config core.autocrlf false +git config core.eol lf +``` + +This tells Git to: +- Never convert line endings automatically +- Always use LF for line endings in working directory + + +#### Fix Existing Repository with Wrong Line Endings + +If you already have CRLF line endings in your local repository, the **best approach** is to configure Git properly and clone the project again: + +1. **Configure Git first**: + ```bash + git config --global core.autocrlf false + git config --global core.eol lf + ``` + +2. **Clone the project fresh** (recommended): + ```bash + # Navigate to parent directory + cd .. + + # Remove current repository (backup your changes first!) + rm -rf docs + + # Clone again with correct line endings + git clone git@github.com:suitenumerique/conversations.git + ``` + +**Alternative**: If you have uncommitted changes and cannot re-clone: + +1. **Backup your changes**: + ```bash + git add . + git commit -m "Save changes before fixing line endings" + ``` + +2. **Remove all files from Git's index**: + ```bash + git rm --cached -r . + ``` + +3. **Reset Git configuration** (if not done globally): + ```bash + git config core.autocrlf false + git config core.eol lf + ``` + +4. **Re-add all files** (Git will use LF line endings): + ```bash + git add . + ``` + +5. **Commit the changes**: + ```bash + git commit -m "โœ๏ธ(project) Fix line endings to LF" + ``` + +## Minio Permission Issues on Windows + +### Problem Description + +On Windows, you may encounter permission-related errors when running Minio in development mode with Docker Compose. This typically happens because: + +- **Windows file permissions** don't map well to Unix-style user IDs used in Docker containers +- **Docker Desktop** may have issues with user mapping when using the `DOCKER_USER` environment variable +- **Minio container** fails to start or access volumes due to permission conflicts + +### Common Symptoms + +- Minio container fails to start with permission denied errors +- Error messages related to file system permissions in Minio logs +- Unable to create or access buckets in the development environment +- Docker Compose showing Minio service as unhealthy or exited + +### Solution for Windows Users + +If you encounter Minio permission issues on Windows, you can temporarily disable user mapping for the Minio service: + +1. **Open the `compose.yml` file** + +2. **Comment out the user directive** in the `minio` service section: + ```yaml + minio: + # user: ${DOCKER_USER:-1000} # Comment this line on Windows if permission issues occur + image: minio/minio + environment: + - MINIO_ROOT_USER=conversations + - MINIO_ROOT_PASSWORD=password + # ... rest of the configuration + ``` + +3. **Restart the services**: + ```bash + make run + ``` + +### Why This Works + +- Commenting out the `user` directive allows the Minio container to run with its default user +- This bypasses Windows-specific permission mapping issues +- The container will have the necessary permissions to access and manage the mounted volumes + +### Note + +This is a **development-only workaround**. In production environments, proper user mapping and security considerations should be maintained according to your deployment requirements. + +## Frontend File Watching Issues on Windows + +### Problem Description + +Windows users may experience issues with file watching in the frontend-development container. This typically happens because: + +- **Docker on Windows** has known limitations with file change detection +- **Node.js file watchers** may not detect changes properly on Windows filesystem +- **Hot reloading** fails to trigger when files are modified + +### Common Symptoms + +- Changes to frontend code aren't detected automatically +- Hot module replacement doesn't work as expected +- Need to manually restart the frontend container after code changes +- Console shows no reaction when saving files + +### Solution: Enable WATCHPACK_POLLING + +Add the `WATCHPACK_POLLING=true` environment variable to the frontend-development service in your local environment: + +1. **Modify the `compose.yml` file** by adding the environment variable to the frontend-development service: + + ```yaml + frontend-development: + user: "${DOCKER_USER:-1000}" + build: + context: . + dockerfile: ./src/frontend/Dockerfile + target: conversations-dev + args: + API_ORIGIN: "http://localhost:8071" + image: conversations:frontend-development + environment: + - WATCHPACK_POLLING=true # Add this line for Windows users + volumes: + - ./src/frontend:/home/frontend + - /home/frontend/node_modules + - /home/frontend/apps/conversations/node_modules + ports: + - "3000:3000" + ``` + +2. **Restart your containers**: + ```bash + make run + ``` + +### Why This Works + +- `WATCHPACK_POLLING=true` forces the file watcher to use polling instead of filesystem events +- Polling periodically checks for file changes rather than relying on OS-level file events +- This is more reliable on Windows but slightly increases CPU usage +- Changes to your frontend code should now be detected properly, enabling hot reloading + +### Note + +This setting is primarily needed for Windows users. Linux and macOS users typically don't need this setting as file watching works correctly by default on those platforms. \ No newline at end of file diff --git a/env.d/development/common.dist b/env.d/development/common.dist new file mode 100644 index 0000000..5216118 --- /dev/null +++ b/env.d/development/common.dist @@ -0,0 +1,57 @@ +# Django +DJANGO_ALLOWED_HOSTS=* +DJANGO_SECRET_KEY=ThisIsAnExampleKeyForDevPurposeOnly +DJANGO_SETTINGS_MODULE=conversations.settings +DJANGO_SUPERUSER_PASSWORD=admin + +# Logging +# Set to DEBUG level for dev only +LOGGING_LEVEL_HANDLERS_CONSOLE=INFO +LOGGING_LEVEL_LOGGERS_ROOT=INFO +LOGGING_LEVEL_LOGGERS_APP=INFO + +# Python +PYTHONPATH=/app + +# conversations settings + +# Mail +DJANGO_EMAIL_BRAND_NAME="La Suite Numรฉrique" +DJANGO_EMAIL_HOST="maildev" +DJANGO_EMAIL_LOGO_IMG="http://localhost:3000/assets/logo-suite-numerique.png" +DJANGO_EMAIL_PORT=1025 + +# Backend url +CONVERSATIONS_BASE_URL="http://localhost:8072" + +# Media +STORAGES_STATICFILES_BACKEND=django.contrib.staticfiles.storage.StaticFilesStorage +AWS_S3_ENDPOINT_URL=http://minio:9000 +AWS_S3_ACCESS_KEY_ID=conversations +AWS_S3_SECRET_ACCESS_KEY=password +MEDIA_BASE_URL=http://localhost:8083 + +# OIDC +OIDC_OP_JWKS_ENDPOINT=http://nginx:8083/realms/conversations/protocol/openid-connect/certs +OIDC_OP_AUTHORIZATION_ENDPOINT=http://localhost:8083/realms/conversations/protocol/openid-connect/auth +OIDC_OP_TOKEN_ENDPOINT=http://nginx:8083/realms/conversations/protocol/openid-connect/token +OIDC_OP_USER_ENDPOINT=http://nginx:8083/realms/conversations/protocol/openid-connect/userinfo + +OIDC_RP_CLIENT_ID=conversations +OIDC_RP_CLIENT_SECRET=ThisIsAnExampleKeyForDevPurposeOnly +OIDC_RP_SIGN_ALGO=RS256 +OIDC_RP_SCOPES="openid email" + +LOGIN_REDIRECT_URL=http://localhost:3000 +LOGIN_REDIRECT_URL_FAILURE=http://localhost:3000 +LOGOUT_REDIRECT_URL=http://localhost:3000 + +OIDC_REDIRECT_ALLOWED_HOSTS=["http://localhost:8083", "http://localhost:3000"] +OIDC_AUTH_REQUEST_EXTRA_PARAMS={"acr_values": "eidas1"} + +# AI +# AI_BASE_URL=https://openaiendpoint.com +AI_API_KEY=password +# AI_MODEL=llama + +ML_FLOW_TRACKING_URI = "http://ml-flow:5050" \ No newline at end of file diff --git a/env.d/development/common.e2e.dist b/env.d/development/common.e2e.dist new file mode 100644 index 0000000..3b2e2af --- /dev/null +++ b/env.d/development/common.e2e.dist @@ -0,0 +1,4 @@ +# For the CI job test-e2e +BURST_THROTTLE_RATES="200/minute" +DJANGO_SERVER_TO_SERVER_API_TOKENS=test-e2e +SUSTAINED_THROTTLE_RATES="200/hour" diff --git a/env.d/development/crowdin.dist b/env.d/development/crowdin.dist new file mode 100644 index 0000000..6c6a9d8 --- /dev/null +++ b/env.d/development/crowdin.dist @@ -0,0 +1,3 @@ +CROWDIN_PERSONAL_TOKEN=Your-Personal-Token +CROWDIN_PROJECT_ID=Your-Project-Id +CROWDIN_BASE_PATH=/app/src diff --git a/env.d/development/kc_postgresql.dist b/env.d/development/kc_postgresql.dist new file mode 100644 index 0000000..2ce2633 --- /dev/null +++ b/env.d/development/kc_postgresql.dist @@ -0,0 +1,11 @@ +# Postgresql db container configuration +POSTGRES_DB=keycloak +POSTGRES_USER=conversations +POSTGRES_PASSWORD=pass + +# App database configuration +DB_HOST=kc_postgresql +DB_NAME=keycloak +DB_USER=conversations +DB_PASSWORD=pass +DB_PORT=5433 \ No newline at end of file diff --git a/env.d/development/postgresql.dist b/env.d/development/postgresql.dist new file mode 100644 index 0000000..012e15c --- /dev/null +++ b/env.d/development/postgresql.dist @@ -0,0 +1,11 @@ +# Postgresql db container configuration +POSTGRES_DB=conversations +POSTGRES_USER=dinum +POSTGRES_PASSWORD=pass + +# App database configuration +DB_HOST=postgresql +DB_NAME=conversations +DB_USER=dinum +DB_PASSWORD=pass +DB_PORT=5432 \ No newline at end of file diff --git a/gitlint/gitlint_emoji.py b/gitlint/gitlint_emoji.py new file mode 100644 index 0000000..c8f7024 --- /dev/null +++ b/gitlint/gitlint_emoji.py @@ -0,0 +1,37 @@ +""" +Gitlint extra rule to validate that the message title is of the form +"() " +""" +from __future__ import unicode_literals + +import re + +import requests + +from gitlint.rules import CommitMessageTitle, LineRule, RuleViolation + + +class GitmojiTitle(LineRule): + """ + This rule will enforce that each commit title is of the form "() " + where gitmoji is an emoji from the list defined in https://gitmoji.carloscuesta.me and + subject should be all lowercase + """ + + id = "UC1" + name = "title-should-have-gitmoji-and-scope" + target = CommitMessageTitle + + def validate(self, title, _commit): + """ + Download the list possible gitmojis from the project's github repository and check that + title contains one of them. + """ + gitmojis = requests.get( + "https://raw.githubusercontent.com/carloscuesta/gitmoji/master/packages/gitmojis/src/gitmojis.json" + ).json()["gitmojis"] + emojis = [item["emoji"] for item in gitmojis] + pattern = r"^({:s})\(.*\)\s[a-zA-Z].*$".format("|".join(emojis)) + if not re.search(pattern, title): + violation_msg = 'Title does not match regex "() "' + return [RuleViolation(self.id, violation_msg, title)] diff --git a/package.json b/package.json new file mode 100644 index 0000000..24afa17 --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "@ai-sdk/react": "^1.2.12", + "@ai-sdk/ui-utils": "^1.2.11" + } +} diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..bbbbfe7 --- /dev/null +++ b/renovate.json @@ -0,0 +1,31 @@ +{ + "extends": ["github>numerique-gouv/renovate-configuration"], + "dependencyDashboard": true, + "labels": ["dependencies", "noChangeLog", "automated"], + "packageRules": [ + { + "enabled": false, + "groupName": "ignored python dependencies", + "matchManagers": ["pep621"], + "matchPackageNames": [] + }, + { + "groupName": "allowed redis versions", + "matchManagers": ["pep621"], + "matchPackageNames": ["redis"], + "allowedVersions": "<6.0.0" + }, + { + "enabled": false, + "groupName": "ignored js dependencies", + "matchManagers": ["npm"], + "matchPackageNames": [ + "eslint", + "fetch-mock", + "node", + "node-fetch", + "workbox-webpack-plugin" + ] + } + ] +} diff --git a/src/backend/.pylintrc b/src/backend/.pylintrc new file mode 100644 index 0000000..9807de7 --- /dev/null +++ b/src/backend/.pylintrc @@ -0,0 +1,472 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code +extension-pkg-whitelist= + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=migrations + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=0 + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins=pylint_django,pylint.extensions.no_self_use + +# Pickle collected data for later comparisons. +persistent=yes + +# Specify a configuration file. +#rcfile= + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +disable=bad-inline-option, + deprecated-pragma, + django-not-configured, + file-ignored, + locally-disabled, + no-self-use, + raw-checker-failed, + suppressed-message, + useless-suppression + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio).You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=optparse.Values,sys.exit + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local,responses, + Template,Contact + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module +max-module-lines=1000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=yes + +# Minimum lines number of a similarity. +# First implementations of CMS wizards have common fields we do not want to factorize for now +min-similarity-lines=35 + + +[BASIC] + +# Naming style matching correct argument names +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style +#argument-rgx= + +# Naming style matching correct attribute names +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Naming style matching correct class attribute names +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style +#class-attribute-rgx= + +# Naming style matching correct class names +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming-style +#class-rgx= + +# Naming style matching correct constant names +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__)|urlpatterns|logger)$ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma +good-names=i, + j, + k, + cm, + ex, + Run, + _ + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no + +# Naming style matching correct inline iteration names +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style +#inlinevar-rgx= + +# Naming style matching correct method names +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style +method-rgx=([a-z_][a-z0-9_]{2,50}|setUp|set[Uu]pClass|tearDown|tear[Dd]ownClass|assert[A-Z]\w*|maxDiff|test_[a-z0-9_]+)$ + +# Naming style matching correct module names +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style +#variable-rgx= + + +[IMPORTS] + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in a if statement +max-bool-expr=5 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of locals for function / method body +max-locals=20 + +# Maximum number of parents for a class (see R0901). +max-parents=10 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of statements in function / method body +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=0 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=builtins.Exception diff --git a/src/backend/MANIFEST.in b/src/backend/MANIFEST.in new file mode 100644 index 0000000..5988c5c --- /dev/null +++ b/src/backend/MANIFEST.in @@ -0,0 +1,3 @@ +include LICENSE +include README.md +recursive-include src/backend/conversations *.html *.png *.gif *.css *.ico *.jpg *.jpeg *.po *.mo *.eot *.svg *.ttf *.woff *.woff2 diff --git a/src/backend/__init__.py b/src/backend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/backend/chat/__init__.py b/src/backend/chat/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/backend/chat/admin.py b/src/backend/chat/admin.py new file mode 100644 index 0000000..cf85ef0 --- /dev/null +++ b/src/backend/chat/admin.py @@ -0,0 +1,16 @@ +"""Admin classes and registrations for chat application.""" + +from django.contrib import admin + +from . import models + + +@admin.register(models.ChatConversation) +class ChatConversationAdmin(admin.ModelAdmin): + """Admin class for the ChatConversation model""" + + list_display = ( + "id", + "created_at", + "updated_at", + ) diff --git a/src/backend/chat/ai_sdk_types.py b/src/backend/chat/ai_sdk_types.py new file mode 100644 index 0000000..f104166 --- /dev/null +++ b/src/backend/chat/ai_sdk_types.py @@ -0,0 +1,451 @@ +"""This module defines the data structures used in the Vercel AI SDK for chat interactions.""" + +from datetime import datetime +from typing import Any, Dict, List, Literal, Optional, Union + +from pydantic import BaseModel + +# JSONValue type +JSONValue = Union[None, str, int, float, bool, Dict[str, Any], List[Any]] + + +# ToolCall and ToolResult +class ToolCall(BaseModel): + """ + Represents a call to a tool with arguments. + + Attributes: + toolCallId: A unique identifier for the tool call. + toolName: The name of the tool being called. + args: The arguments passed to the tool. + """ + + toolCallId: str + toolName: str + args: Dict[str, Any] + + +class ToolResult(BaseModel): + """ + Represents the result of a tool call including the original call details. + + Attributes: + toolCallId: A unique identifier for the tool call. + toolName: The name of the tool that was called. + args: The arguments that were passed to the tool. + result: The result returned by the tool. + """ + + toolCallId: str + toolName: str + args: Dict[str, Any] + result: Any + + +# ToolInvocation union +class ToolInvocationPartialCall(ToolCall): + """ + Represents a tool call that is in progress with partial arguments. + + Attributes: + state: The state of the tool invocation, fixed to 'partial-call'. + step: Optional step number to track the sequence of tool invocations. + """ + + state: Literal["partial-call"] + step: Optional[int] = None + + +class ToolInvocationCall(ToolCall): + """ + Represents a complete tool call ready for execution. + + Attributes: + state: The state of the tool invocation, fixed to 'call'. + step: Optional step number to track the sequence of tool invocations. + """ + + state: Literal["call"] + step: Optional[int] = None + + +class ToolInvocationResult(ToolResult): + """ + Represents a completed tool call with its result. + + Attributes: + state: The state of the tool invocation, fixed to 'result'. + step: Optional step number to track the sequence of tool invocations. + """ + + state: Literal["result"] + step: Optional[int] = None + + +ToolInvocation = Union[ToolInvocationPartialCall, ToolInvocationCall, ToolInvocationResult] + + +# Attachment +class Attachment(BaseModel): + """ + Represents a file attachment that can be sent with a message. + + Attributes: + name: Optional name of the attachment, usually the filename. + contentType: Optional MIME type of the attachment. + url: The URL of the attachment, can be a hosted URL or Data URL. + """ + + name: Optional[str] = None + contentType: Optional[str] = None + url: str + + +# Reasoning details +class ReasoningDetailText(BaseModel): + """ + Represents a text-based reasoning detail in a message. + + Attributes: + type: The type of reasoning detail, fixed to 'text'. + text: The text content of the reasoning. + signature: Optional signature associated with the reasoning. + """ + + type: Literal["text"] + text: str + signature: Optional[str] = None + + +class ReasoningDetailRedacted(BaseModel): + """ + Represents a redacted reasoning detail in a message. + + Attributes: + type: The type of reasoning detail, fixed to 'redacted'. + data: The redacted content. + """ + + type: Literal["redacted"] + data: str + + +ReasoningDetail = Union[ReasoningDetailText, ReasoningDetailRedacted] + + +# UIParts +class TextUIPart(BaseModel): + """ + Represents a text part of a message. + + Attributes: + type: The type of UI part, fixed to 'text'. + text: The text content. + """ + + type: Literal["text"] + text: str + + +class ReasoningUIPart(BaseModel): + """ + Represents a reasoning part of a message. + + Attributes: + type: The type of UI part, fixed to 'reasoning'. + reasoning: The reasoning text. + details: List of reasoning details. + """ + + type: Literal["reasoning"] + reasoning: str + details: List[ReasoningDetail] + + +class ToolInvocationUIPart(BaseModel): + """ + Represents a tool invocation part of a message. + + Attributes: + type: The type of UI part, fixed to 'tool-invocation'. + toolInvocation: The tool invocation details. + """ + + type: Literal["tool-invocation"] + toolInvocation: ToolInvocation + + +class LanguageModelV1Source(BaseModel): + """ + Represents source information from a language model. + + Attributes: + source_type: The type of source. + details: Additional details about the source. + """ + + source_type: str + details: Dict[str, Any] + + +class SourceUIPart(BaseModel): + """ + Represents a source part of a message. + + Attributes: + type: The type of UI part, fixed to 'source'. + source: The source information. + """ + + type: Literal["source"] + source: LanguageModelV1Source + + +class FileUIPart(BaseModel): + """ + Represents a file part of a message. + + Attributes: + type: The type of UI part, fixed to 'file'. + mimeType: The MIME type of the file. + data: The file data. + """ + + type: Literal["file"] + mimeType: str + data: str + + +class StepStartUIPart(BaseModel): + """ + Represents a step boundary part of a message. + + Attributes: + type: The type of UI part, fixed to 'step-start'. + """ + + type: Literal["step-start"] + + +UIPart = Union[ + TextUIPart, + ReasoningUIPart, + ToolInvocationUIPart, + SourceUIPart, + FileUIPart, + StepStartUIPart, +] + + +# Message and related types +class Message(BaseModel): + """ + Represents a message in a chat conversation. + + Attributes: + id: A unique identifier for the message. + createdAt: Optional timestamp when the message was created. + experimental_attachments: Optional list of attachments. + role: The role of the sender (system, user, assistant, or data). + annotations: Optional list of annotations. + parts: Optional list of UI parts that make up the message content. + """ + + id: str + createdAt: Optional[datetime] = None + content: str # deprecated, use parts instead + reasoning: Optional[str] = None # deprecated, use parts instead + experimental_attachments: Optional[List[Attachment]] = None + role: Literal["system", "user", "assistant", "data"] + # data: Optional[JSONValue] = None + annotations: Optional[List[JSONValue]] = None + toolInvocations: Optional[List[ToolInvocation]] = None # deprecated, use parts instead + parts: Optional[List[UIPart]] = None + + +class UIMessage(Message): + """ + Represents a message with UI parts for rendering in the user interface. + + Attributes: + parts: List of UI parts that make up the message content. + """ + + parts: List[UIPart] + + +class CreateMessage(BaseModel): + """ + Model for creating a new message. + + Attributes: + createdAt: Optional timestamp when the message was created. + content: The text content of the message. + reasoning: Optional reasoning for the message. + experimental_attachments: Optional list of attachments. + role: The role of the sender (system, user, assistant, or data). + data: Optional JSON value for data messages. + annotations: Optional list of annotations. + toolInvocations: Optional list of tool invocations. + parts: Optional list of UI parts that make up the message content. + id: Optional unique identifier for the message. + """ + + createdAt: Optional[datetime] = None + content: str + reasoning: Optional[str] = None + experimental_attachments: Optional[List[Attachment]] = None + role: Literal["system", "user", "assistant", "data"] + data: Optional[JSONValue] = None + annotations: Optional[List[JSONValue]] = None + toolInvocations: Optional[List[ToolInvocation]] = None + parts: Optional[List[UIPart]] = None + id: Optional[str] = None + + +class ChatRequest(BaseModel): + """ + Represents a request to the chat API. + + Attributes: + headers: Optional request headers. + body: Optional request body. + messages: List of messages in the conversation. + data: Optional additional data for the request. + """ + + headers: Optional[Dict[str, str]] = None + body: Optional[Dict[str, Any]] = None + messages: List[Message] + data: Optional[JSONValue] = None + + +class ChatRequestOptions(BaseModel): + """ + Options for a chat request. + + Attributes: + headers: Optional request headers. + body: Optional request body. + data: Optional additional data for the request. + experimental_attachments: Optional list of attachments. + allowEmptySubmit: Optional flag to allow empty message submission. + """ + + headers: Optional[Dict[str, str]] = None + body: Optional[Dict[str, Any]] = None + data: Optional[JSONValue] = None + experimental_attachments: Optional[List[Attachment]] = None + allowEmptySubmit: Optional[bool] = None + + +class UseChatOptions(BaseModel): + """ + Options for the useChat hook. + + Attributes: + keepLastMessageOnError: Optional flag to keep the last message on error. + api: Optional API endpoint. + id: Optional unique identifier for the chat. + initialMessages: Optional initial messages for the chat. + initialInput: Optional initial input for the chat. + credentials: Optional credentials for the request. + headers: Optional request headers. + body: Optional request body. + sendExtraMessageFields: Optional flag to send extra message fields. + streamProtocol: Optional stream protocol to use. + """ + + keepLastMessageOnError: Optional[bool] = None + api: Optional[str] = None + id: Optional[str] = None + initialMessages: Optional[List[Message]] = None + initialInput: Optional[str] = None + credentials: Optional[str] = None + headers: Optional[Dict[str, str]] = None + body: Optional[Dict[str, Any]] = None + sendExtraMessageFields: Optional[bool] = None + streamProtocol: Optional[Literal["data", "text"]] = None + + +class UseCompletionOptions(BaseModel): + """ + Options for the useCompletion hook. + + Attributes: + api: Optional API endpoint. + id: Optional unique identifier for the completion. + initialInput: Optional initial input for the completion. + initialCompletion: Optional initial completion result. + credentials: Optional credentials for the request. + headers: Optional request headers. + body: Optional request body. + streamProtocol: Optional stream protocol to use. + """ + + api: Optional[str] = None + id: Optional[str] = None + initialInput: Optional[str] = None + initialCompletion: Optional[str] = None + credentials: Optional[str] = None + headers: Optional[Dict[str, str]] = None + body: Optional[Dict[str, Any]] = None + streamProtocol: Optional[Literal["data", "text"]] = None + + +class LanguageModelUsage(BaseModel): + """ + Represents the token usage in a language model interaction. + + Attributes: + promptTokens: Number of tokens used in the prompt. + completionTokens: Number of tokens used in the completion. + totalTokens: Total number of tokens used. + """ + + promptTokens: int + completionTokens: int + totalTokens: int + + +class AssistantMessageContentText(BaseModel): + """ + Represents text content in an assistant message. + + Attributes: + type: The type of content, fixed to 'text'. + text: Dictionary containing the text value. + """ + + type: Literal["text"] + text: Dict[str, str] # {'value': str} + + +class AssistantMessage(BaseModel): + """ + Represents a message from the assistant. + + Attributes: + id: A unique identifier for the message. + role: The role of the sender, fixed to 'assistant'. + content: List of content blocks in the message. + """ + + id: str + role: Literal["assistant"] + content: List[AssistantMessageContentText] + + +class DataMessage(BaseModel): + """ + Represents a data message. + + Attributes: + id: Optional unique identifier for the message. + role: The role of the sender, fixed to 'data'. + data: The JSON data contained in the message. + """ + + id: Optional[str] = None + role: Literal["data"] + data: JSONValue diff --git a/src/backend/chat/apps.py b/src/backend/chat/apps.py new file mode 100644 index 0000000..ad56a48 --- /dev/null +++ b/src/backend/chat/apps.py @@ -0,0 +1,12 @@ +"""Chat application""" + +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class ChatDefaultConfig(AppConfig): + """Configuration class for the chat application.""" + + name = "chat" + app_label = "chat" + verbose_name = _("chat application") diff --git a/src/backend/chat/clients/__init__.py b/src/backend/chat/clients/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/backend/chat/clients/agent.py b/src/backend/chat/clients/agent.py new file mode 100644 index 0000000..2b6ab0b --- /dev/null +++ b/src/backend/chat/clients/agent.py @@ -0,0 +1,382 @@ +"""AIAgentService class for handling AI agent interactions.""" + +import asyncio +import json +import logging +import queue +import threading +import uuid +from contextlib import AsyncExitStack +from typing import List + +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured + +from agents import Agent, ModelResponse, OpenAIChatCompletionsModel, Runner, Usage +from asgiref.sync import sync_to_async +from openai import AsyncOpenAI +from openai.types.responses import ResponseInputItemParam, ResponseOutputItem +from openai.types.responses.response_usage import ( + InputTokensDetails, + OutputTokensDetails, + ResponseUsage, +) + +from chat.ai_sdk_types import ( + TextUIPart, + ToolInvocationPartialCall, + ToolInvocationResult, + ToolInvocationUIPart, + UIMessage, +) +from chat.mcp_servers import get_mcp_servers +from chat.tools import agent_get_current_weather + +logger = logging.getLogger(__name__) + + +def convert_async_generator_to_sync(async_gen): + """Convert an async generator to a sync generator.""" + q = queue.Queue() + sentinel = object() + + async def run_async_gen(): + try: + async for item in async_gen: + q.put(item) + finally: + q.put(sentinel) + + def start_async_loop(): + asyncio.run(run_async_gen()) + + thread = threading.Thread(target=start_async_loop) + thread.start() + + while True: + item = q.get() + if item is sentinel: + break + yield item + + thread.join() + + +class AIAgentService: + """Service class for AI-related operations.""" + + def __init__(self, conversation): + """Ensure that the AI configuration is set properly.""" + if settings.AI_BASE_URL is None or settings.AI_API_KEY is None or settings.AI_MODEL is None: + raise ImproperlyConfigured("AIChatService configuration not set") + + self.model = OpenAIChatCompletionsModel( + model=settings.AI_MODEL, + openai_client=AsyncOpenAI( + base_url=settings.AI_BASE_URL, + api_key=settings.AI_API_KEY, + ), + ) + self.conversation = conversation + + @staticmethod + def _convert_to_openai_messages( # noqa: PLR0912 + messages: List[UIMessage], + ) -> List[ResponseInputItemParam]: + openai_messages = [] + + for message in messages: + content_parts = [] + tool_calls = [] + + for part in message.parts: + match part.type: + case "text": + content_parts.append({"type": "input_text", "text": part.text}) + + case "image": + content_parts.append( + { + "type": "input_image", + "image_url": part.url, + } + ) + + case "file": + content_parts.append( + { + "type": "input_file", + "file_data": part.url, + "filename": part.name, + } + ) + + case "tool-invocation": + # Extract the tool invocation data + tool_invocation = part.toolInvocation + tool_calls.append( + { + "call_id": tool_invocation.toolCallId, + "type": "function_call", + "name": tool_invocation.toolName, + "arguments": json.dumps(tool_invocation.args), + "status": tool_invocation.state, + } + ) + + case _: + logger.warning("Unrecognized part type: %s in part: %s", part.type, part) + + # Add experimental attachments if they exist + if hasattr(message, "experimental_attachments") and message.experimental_attachments: + for attachment in message.experimental_attachments: + if attachment.contentType.startswith("image"): + content_parts.append( + { + "type": "input_image", + "image_url": attachment.url, + } + ) + elif attachment.contentType.startswith("text"): + content_parts.append( + { + "type": "input_file", + "file_data": attachment.url, + "filename": attachment.name, + } + ) + + # Add tool calls separately + for tool_call in tool_calls: + openai_messages.append(tool_call) + + # Add message with content parts if there are any + if content_parts: + openai_messages.append({"role": message.role, "content": content_parts}) + + return openai_messages + + def stream_text(self, messages: List[UIMessage]): + """Simple generator to convert async generator to sync generator.""" + async_generator = self.stream_text_async(messages) + return convert_async_generator_to_sync(async_generator) + + async def stream_text_async(self, messages: List[UIMessage]): + """Async generator for streaming agent events.""" + openai_messages = self._convert_to_openai_messages(messages) + logger.info("[stream_data_async] Received messages: %s", openai_messages) + + mcp_servers = get_mcp_servers() + + async with mcp_servers[0] as mcp_server: + agent = Agent( + name=settings.AI_AGENT_NAME, + instructions=settings.settings.AI_AGENT_INSTRUCTIONS, + model=self.model, + tools=[agent_get_current_weather], + mcp_servers=[mcp_server], + ) + result = Runner.run_streamed( + agent, + input=openai_messages, + ) + + async for event in result.stream_events(): + logger.info("[stream_text_async] Received event: %s", event) + if event.type == "raw_response_event": + data = event.data + logger.info("[stream_text_async] - data: %s", data) + if data.type == "response.output_text.delta": + yield data.delta + + # At the end, save the response and yield the finish message part + _response_usage = Usage() + for raw_response in result.raw_responses: + _response_usage.add(raw_response.usage) + + await sync_to_async(self._update_conversation)( + openai_messages, result.raw_responses, _response_usage + ) + + def stream_data(self, messages: List[UIMessage]): + """Simple generator to convert async generator to sync generator.""" + async_generator = self.stream_data_async(messages) + return convert_async_generator_to_sync(async_generator) + + async def stream_data_async(self, messages: List[UIMessage]): + """Async generator for streaming agent events.""" + finish_reason = "stop" + + openai_messages = self._convert_to_openai_messages(messages) + logger.info("[stream_data_async] Received messages: %s", openai_messages) + + async with AsyncExitStack() as stack: + initialized_mcp_servers = [ + await stack.enter_async_context(mcp_server) for mcp_server in get_mcp_servers() + ] + + agent = Agent( + name=settings.AI_AGENT_NAME, + instructions=settings.AI_AGENT_INSTRUCTIONS, + model=self.model, + #tools=[agent_get_current_weather], + mcp_servers=initialized_mcp_servers, + ) + result = Runner.run_streamed( + agent, + input=openai_messages, + ) + + try: + async for event in result.stream_events(): + # logger.info("[stream_data_async] Received event: %s", event) + + if event.type == "raw_response_event": + data = event.data + # logger.info("[stream_data_async] - data: %s", data) + if data.type == "response.output_text.delta": + yield f"0:{json.dumps(data.delta)}\n" + + if hasattr(data, "finish_reason") and data.finish_reason: + finish_reason = data.finish_reason + + elif event.type == "run_item_stream_event": + item = event.item + if item.type == "tool_call_item": + _tool_call = { + "toolCallId": item.raw_item.call_id, + "toolName": item.raw_item.name, + "args": ( + json.loads(item.raw_item.arguments) + if hasattr(item.raw_item, "arguments") + else {} + ), + } + yield f"9:{json.dumps(_tool_call)}\n" + elif item.type == "tool_call_output_item": + _tool_call_result = { + "toolCallId": str(item.raw_item["call_id"]), + "result": item.raw_item["output"], + } + yield f"a:{json.dumps(_tool_call_result)}\n" + elif event.type == "agent_updated_stream_event": + logger.info( + "[stream_data_async] Agent switched to: %s", event.new_agent.name + ) + + except Exception as e: # pylint: disable=broad-except + logger.exception("Error in stream_data_async") + yield f"3:{json.dumps(str(e))}\n" + finish_reason = "error" + + # At the end, save the response and yield the finish message part + _response_usage = Usage() + for raw_response in result.raw_responses: + _response_usage.add(raw_response.usage) + + await sync_to_async(self._update_conversation)( + openai_messages, result.raw_responses, _response_usage + ) + + _finish_message = { + "finishReason": finish_reason, + "usage": { + "promptTokens": _response_usage.input_tokens, + "completionTokens": _response_usage.output_tokens, + }, + } + yield f"d:{json.dumps(_finish_message)}\n" + + def _update_conversation( + self, + input_messages: List[ResponseInputItemParam], + result_raw_responses: List[ModelResponse], + response_usage: Usage, + ): + ui_messages = [] + + self.conversation.openai_messages = input_messages + [ + output.model_dump() + for raw_response in result_raw_responses + for output in raw_response.output + ] + + for raw_response in result_raw_responses: + ui_messages += self._convert_openai_output_to_ui_messages(raw_response.output) + + self.conversation.messages = self.conversation.ui_messages + [ + ui_message.model_dump() for ui_message in ui_messages + ] + + if self.conversation.agent_usage: + total_usage = ResponseUsage(**self.conversation.agent_usage) + else: + total_usage = ResponseUsage( + input_tokens=0, + output_tokens=0, + input_tokens_details=InputTokensDetails(cached_tokens=0), + output_tokens_details=OutputTokensDetails(reasoning_tokens=0), + total_tokens=0, + ) + + total_usage.input_tokens += response_usage.input_tokens # pylint: disable=no-member + total_usage.output_tokens += response_usage.output_tokens # pylint: disable=no-member + total_usage.input_tokens_details.cached_tokens += ( + response_usage.input_tokens_details.cached_tokens + ) + total_usage.output_tokens_details.reasoning_tokens += ( + response_usage.output_tokens_details.reasoning_tokens + ) + total_usage.total_tokens = response_usage.total_tokens + + self.conversation.agent_usage = total_usage.model_dump() + + self.conversation.save() + + def _convert_openai_output_to_ui_messages( + self, output: List[ResponseOutputItem] + ) -> List[UIMessage]: + """Convert OpenAI output to UI messages.""" + ui_messages = [] + + for item in output: + if item.type == "message": + text_parts = [TextUIPart(type="text", text=item.text) for item in item.content] + ui_messages.append( + UIMessage( + id=str(uuid.uuid4()), + role="assistant", + parts=text_parts, + content="".join(part.text for part in text_parts), + ) + ) + elif item.type == "function_call": + if item.status == "in_progress": + tool_invocation = ToolInvocationPartialCall( + state="partial-call", + step=None, + toolCallId=item.call_id, + toolName=item.name, + args=json.loads(item.arguments), + ) + elif item.status == "completed": + tool_invocation = ToolInvocationResult( + state="result", + step=None, + toolCallId=item.call_id, + toolName=item.name, + args=json.loads(item.arguments), + result=json.loads(item.result) if item.result else None, + ) + # elif item.status == "incomplete": + else: + logger.warning("[stream_data_async] Unhandled message: %s", item) + continue + + ui_tool_invocation = ToolInvocationUIPart( + type="tool-invocation", + toolInvocation=tool_invocation, + ) + ui_messages.append(UIMessage(role="assistant", parts=[ui_tool_invocation])) + # Handle other types as needed + return ui_messages diff --git a/src/backend/chat/mcp_servers.py b/src/backend/chat/mcp_servers.py new file mode 100644 index 0000000..9e5794e --- /dev/null +++ b/src/backend/chat/mcp_servers.py @@ -0,0 +1,23 @@ +"""MCP servers configuration: will be replaced by models.""" + +from agents.mcp import MCPServerStreamableHttp, MCPServerStreamableHttpParams + +MCP_SERVERS = { + "mcpServers": { + # "github": { + # "url": "https://api.githubcopilot.com/mcp/", + # "headers": {"Authorization": "Bearer XXX"}, + # }, + } +} + + +def get_mcp_servers(): + """Retrieve MCP servers configuration.""" + return [ + MCPServerStreamableHttp( + name=name, + params=MCPServerStreamableHttpParams(**server), + ) + for name, server in MCP_SERVERS["mcpServers"].items() + ] diff --git a/src/backend/chat/migrations/0001_initial.py b/src/backend/chat/migrations/0001_initial.py new file mode 100644 index 0000000..f933c76 --- /dev/null +++ b/src/backend/chat/migrations/0001_initial.py @@ -0,0 +1,90 @@ +# Generated by Django 5.2.3 on 2025-06-26 12:15 + +import uuid + +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="ChatConversation", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + help_text="primary key for the record as UUID", + primary_key=True, + serialize=False, + verbose_name="id", + ), + ), + ( + "created_at", + models.DateTimeField( + auto_now_add=True, + help_text="date and time at which a record was created", + verbose_name="created on", + ), + ), + ( + "updated_at", + models.DateTimeField( + auto_now=True, + help_text="date and time at which a record was last updated", + verbose_name="updated on", + ), + ), + ( + "title", + models.CharField( + blank=True, + help_text="Title of the chat conversation", + max_length=100, + null=True, + ), + ), + ( + "ui_messages", + models.JSONField( + blank=True, + default=list, + help_text="UI messages for the chat conversation, sent by frontend, not used", + ), + ), + ( + "openai_messages", + models.JSONField( + blank=True, + default=list, + help_text="OpenAI messages for the chat conversation, not used", + ), + ), + ( + "messages", + models.JSONField( + blank=True, + default=list, + help_text="Stored messages for the chat conversation, sent to frontend", + ), + ), + ( + "agent_usage", + models.JSONField( + blank=True, + default=dict, + help_text="Agent usage for the chat conversation, provided by OpenAI API", + ), + ), + ], + options={ + "abstract": False, + }, + ), + ] diff --git a/src/backend/chat/migrations/0002_initial.py b/src/backend/chat/migrations/0002_initial.py new file mode 100644 index 0000000..70c6f98 --- /dev/null +++ b/src/backend/chat/migrations/0002_initial.py @@ -0,0 +1,26 @@ +# Generated by Django 5.2.3 on 2025-06-26 12:15 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + ("chat", "0001_initial"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name="chatconversation", + name="owner", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="conversations", + to=settings.AUTH_USER_MODEL, + ), + ), + ] diff --git a/src/backend/chat/migrations/__init__.py b/src/backend/chat/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/backend/chat/models.py b/src/backend/chat/models.py new file mode 100644 index 0000000..e8e9a27 --- /dev/null +++ b/src/backend/chat/models.py @@ -0,0 +1,61 @@ +"""Models for chat conversations.""" + +from django.contrib.auth import get_user_model +from django.db import models + +from core.models import BaseModel + +User = get_user_model() + + +class ChatConversation(BaseModel): + """ + Model representing a chat conversation. + + This model stores the details of a chat conversation: + - `owner`: The user who owns the conversation. + - `title`: An optional title for the conversation, provided by frontend, + the 100 first characters of the first user input message. + - `ui_messages`: A JSON field of UI messages sent by the frontend, all content is + overridden at each new request from the frontend. + - `openai_messages`: A JSON field of OpenAI messages, only for debug purpose, not used. + - `messages`: A JSON field of stored messages for the conversation, sent to frontend + when loading the conversation. + - `agent_usage`: A JSON field of agent usage statistics for the conversation, + """ + + owner = models.ForeignKey( + User, + related_name="conversations", + on_delete=models.CASCADE, + null=False, + blank=False, + ) + title = models.CharField( + max_length=100, + blank=True, + null=True, + help_text="Title of the chat conversation", + ) + + ui_messages = models.JSONField( + default=list, + blank=True, + help_text="UI messages for the chat conversation, sent by frontend, not used", + ) + openai_messages = models.JSONField( + default=list, + blank=True, + help_text="OpenAI messages for the chat conversation, not used", + ) + messages = models.JSONField( + default=list, + blank=True, + help_text="Stored messages for the chat conversation, sent to frontend", + ) + + agent_usage = models.JSONField( + default=dict, + blank=True, + help_text="Agent usage for the chat conversation, provided by OpenAI API", + ) diff --git a/src/backend/chat/serializers.py b/src/backend/chat/serializers.py new file mode 100644 index 0000000..a12b29c --- /dev/null +++ b/src/backend/chat/serializers.py @@ -0,0 +1,16 @@ +"""Serializers for chat application.""" + +from rest_framework import serializers + +from chat import models + + +class ChatConversationSerializer(serializers.ModelSerializer): + """Serializer for chat conversations.""" + + owner = serializers.HiddenField(default=serializers.CurrentUserDefault()) + + class Meta: # pylint: disable=missing-class-docstring + model = models.ChatConversation + fields = ["id", "title", "created_at", "updated_at", "messages", "owner"] + read_only_fields = ["id", "created_at", "updated_at", "messages"] diff --git a/src/backend/chat/tools.py b/src/backend/chat/tools.py new file mode 100644 index 0000000..16468c7 --- /dev/null +++ b/src/backend/chat/tools.py @@ -0,0 +1,39 @@ +"""Tools for the chat agent.""" + +from agents import function_tool +from openai.types.chat import ChatCompletionToolParam +from openai.types.shared_params import FunctionDefinition + +current_weather = ChatCompletionToolParam( + type="function", + function=FunctionDefinition( + name="get_current_weather", + description="Get the current weather in a given location", + parameters={ + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The city and state, e.g. San Francisco, CA", + }, + "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}, + }, + "required": ["location", "unit"], + }, + ), +) + + +def get_current_weather(location: str, unit: str): + """Get the current weather in a given location.""" + return { + "location": location, + "temperature": 22 if unit == "celsius" else 72, + "unit": unit, + } + + +@function_tool(name_override="get_current_weather") +def agent_get_current_weather(location: str, unit: str) -> dict: + """Get the current weather in a given location.""" + return get_current_weather(location, unit) diff --git a/src/backend/chat/views.py b/src/backend/chat/views.py new file mode 100644 index 0000000..7c5119e --- /dev/null +++ b/src/backend/chat/views.py @@ -0,0 +1,126 @@ +"""Chat API implementation.""" + +import logging + +from django.conf import settings +from django.http import StreamingHttpResponse + +from rest_framework import decorators, filters, mixins, permissions, status, viewsets +from rest_framework.response import Response + +from core.api.viewsets import Pagination +from core.filters import remove_accents + +from chat import models, serializers +from chat.ai_sdk_types import UIMessage +from chat.clients.agent import AIAgentService + +logger = logging.getLogger(__name__) + + +class ChatConversationFilter(filters.BaseFilterBackend): + """Filter conversation.""" + + def filter_queryset(self, request, queryset, view): + """Filter conversation by title.""" + if title := request.GET.get("title"): + queryset = queryset.filter(title__unaccent__icontains=remove_accents(title)) + return queryset + + +class ChatViewSet( + mixins.CreateModelMixin, + mixins.RetrieveModelMixin, + mixins.ListModelMixin, + mixins.DestroyModelMixin, + mixins.UpdateModelMixin, + viewsets.GenericViewSet, +): + """ViewSet for managing chat conversations. + + Provides endpoints to create, retrieve, list, update, and delete chat conversations. + + The chat conversations are filtered by the authenticated user. + The `post_conversation` action allows sending messages to the chat and receiving a + streaming response with "data" formatted for Vercel AI SDK + see https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol#data-stream-protocol. + """ + + pagination_class = Pagination + permission_classes = [ + permissions.IsAuthenticated, + ] + serializer_class = serializers.ChatConversationSerializer + filter_backends = [filters.OrderingFilter, ChatConversationFilter] + ordering = ["-created_at"] + ordering_fields = ["created_at", "updated_at"] + + def get_queryset(self): + """Return the queryset for the chat conversations.""" + return models.ChatConversation.objects.filter(owner=self.request.user) + + @decorators.action( + methods=["post"], + detail=True, + url_path="conversation", + url_name="conversation", + ) + def post_conversation(self, request, pk): # pylint: disable=unused-argument + """Handle POST requests to the chat endpoint. + + Args: + request: The HTTP request object containing: + - messages: List of message objects with role and content + - protocol: Optional protocol parameter ('text' or 'data', defaults to 'data') + pk: The primary key of the chat conversation. + + Returns: + StreamingHttpResponse: A streaming response containing the chat completion + """ + logger.info("Received messages: %s", request.data.get("messages", [])) + + if settings.ML_FLOW_TRACKING_URI: + # Set up MLflow experiment, don't import it globally to avoid issues + # when running management commands when MLflow is not started + import mlflow # pylint: disable=import-outside-toplevel + + mlflow.set_tracking_uri(settings.ML_FLOW_TRACKING_URI) + mlflow.set_experiment(settings.ML_FLOW_EXPERIMENT_NAME) + + # Enable automatic tracing for all OpenAI API calls + mlflow.openai.autolog() + + # Warning: the messages should be stored more securely in production + conversation = self.get_object() + conversation.ui_messages = request.data.get("messages", []) + conversation.save() + + messages = [UIMessage(**msg) for msg in request.data.get("messages", [])] + protocol = request.query_params.get("protocol", "data") + + logger.info("Received messages: %s", messages) + logger.info("Using protocol: %s", protocol) + + if not messages: + return Response({"error": "No messages provided"}, status=status.HTTP_400_BAD_REQUEST) + + if protocol not in ["text", "data"]: + return Response( + {"error": "Invalid protocol. Must be 'text' or 'data'"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + ai_service = AIAgentService(conversation=conversation) + if protocol == "data": + streaming_content = ai_service.stream_data(messages) + else: + streaming_content = ai_service.stream_text(messages) + + response = StreamingHttpResponse( + streaming_content, + content_type="text/event-stream", + headers={ + "x-vercel-ai-data-stream": "v1", # This header is used for Vercel AI streaming, + }, + ) + return response diff --git a/src/backend/conversations/__init__.py b/src/backend/conversations/__init__.py new file mode 100644 index 0000000..c208e4e --- /dev/null +++ b/src/backend/conversations/__init__.py @@ -0,0 +1 @@ +"""Conversations package""" diff --git a/src/backend/conversations/configuration/theme/default.json b/src/backend/conversations/configuration/theme/default.json new file mode 100644 index 0000000..c0f837d --- /dev/null +++ b/src/backend/conversations/configuration/theme/default.json @@ -0,0 +1,121 @@ +{ + "footer": { + "default": { + "logo": { + "src": "/assets/icon-docs.svg", + "width": "54px", + "alt": "Docs Logo", + "withTitle": true + }, + "externalLinks": [ + { + "label": "Github", + "href": "https://github.com/suitenumerique/conversations/" + }, + { + "label": "DINUM", + "href": "https://www.numerique.gouv.fr/dinum/" + } + ], + "bottomInformation": { + "label": "Unless otherwise stated, all content on this site is under", + "link": { + "label": "licence etalab-2.0", + "href": "https://github.com/etalab/licence-ouverte/blob/master/LO.md" + } + } + }, + "en": { + "legalLinks": [ + { + "label": "Legal Notice", + "href": "#" + }, + { + "label": "Personal data and cookies", + "href": "#" + }, + { + "label": "Accessibility", + "href": "#" + } + ], + "bottomInformation": { + "label": "Unless otherwise stated, all content on this site is under", + "link": { + "label": "licence MIT", + "href": "https://github.com/suitenumerique/conversations/blob/main/LICENSE" + } + } + }, + "fr": { + "legalLinks": [ + { + "label": "Mentions lรฉgales", + "href": "#" + }, + { + "label": "Donnรฉes personnelles et cookies", + "href": "#" + }, + { + "label": "Accessibilitรฉ", + "href": "#" + } + ], + "bottomInformation": { + "label": "Sauf mention contraire, tout le contenu de ce site est sous", + "link": { + "label": "licence MIT", + "href": "https://github.com/suitenumerique/conversations/blob/main/LICENSE" + } + } + }, + "de": { + "legalLinks": [ + { + "label": "Impressum", + "href": "#" + }, + { + "label": "Personenbezogene Daten und Cookies", + "href": "#" + }, + { + "label": "Barrierefreiheit", + "href": "#" + } + ], + "bottomInformation": { + "label": "Sofern nicht anders angegeben, steht der gesamte Inhalt dieser Website unter", + "link": { + "label": "licence MIT", + "href": "https://github.com/suitenumerique/conversations/blob/main/LICENSE" + } + } + }, + "nl": { + "legalLinks": [ + { + "label": "Wettelijke bepalingen", + "href": "#" + }, + { + "label": "Persoonlijke gegevens en cookies", + "href": "#" + }, + { + "label": "Toegankelijkheid", + "href": "#" + } + ], + "bottomInformation": { + "label": "Tenzij anders vermeld, is alle inhoud van deze site ondergebracht onder", + "link": { + "label": "licence MIT", + "href": "https://github.com/suitenumerique/conversations/blob/main/LICENSE" + } + } + } + } +} diff --git a/src/backend/conversations/settings.py b/src/backend/conversations/settings.py new file mode 100755 index 0000000..328e081 --- /dev/null +++ b/src/backend/conversations/settings.py @@ -0,0 +1,799 @@ +""" +Django settings for conversations project. + +Generated by 'django-admin startproject' using Django 3.1.5. + +For more information on this file, see +https://docs.djangoproject.com/en/3.1/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/3.1/ref/settings/ +""" + +import os +import tomllib +from socket import gethostbyname, gethostname + +import sentry_sdk +from configurations import Configuration, values +from sentry_sdk.integrations.django import DjangoIntegration +from sentry_sdk.integrations.logging import ignore_logger + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +DATA_DIR = os.getenv("DATA_DIR", os.path.join("/", "data")) + + +def get_release(): + """ + Get the current release of the application + """ + try: + with open(os.path.join(BASE_DIR, "pyproject.toml"), "rb") as f: + pyproject_data = tomllib.load(f) + return pyproject_data["project"]["version"] + except (FileNotFoundError, KeyError): + return "NA" # Default: not available + + +class Base(Configuration): + """ + This is the base configuration every configuration (aka environment) should inherit from. It + is recommended to configure third-party applications by creating a configuration mixins in + ./configurations and compose the Base configuration with those mixins. + + It depends on an environment variable that SHOULD be defined: + + * DJANGO_SECRET_KEY + + You may also want to override default configuration by setting the following environment + variables: + + * SENTRY_DSN + * DB_NAME + * DB_HOST + * DB_PASSWORD + * DB_USER + """ + + DEBUG = False + USE_SWAGGER = False + + API_VERSION = "v1.0" + + # Security + ALLOWED_HOSTS = values.ListValue([]) + SECRET_KEY = values.Value(None) + SERVER_TO_SERVER_API_TOKENS = values.ListValue([]) + + # Application definition + ROOT_URLCONF = "conversations.urls" + WSGI_APPLICATION = "conversations.wsgi.application" + + # Database + DATABASES = { + "default": { + "ENGINE": values.Value( + "django.db.backends.postgresql_psycopg2", + environ_name="DB_ENGINE", + environ_prefix=None, + ), + "NAME": values.Value("conversations", environ_name="DB_NAME", environ_prefix=None), + "USER": values.Value("dinum", environ_name="DB_USER", environ_prefix=None), + "PASSWORD": values.Value("pass", environ_name="DB_PASSWORD", environ_prefix=None), + "HOST": values.Value("localhost", environ_name="DB_HOST", environ_prefix=None), + "PORT": values.Value(5432, environ_name="DB_PORT", environ_prefix=None), + } + } + DEFAULT_AUTO_FIELD = "django.db.models.AutoField" + + # Static files (CSS, JavaScript, Images) + STATIC_URL = "/static/" + STATIC_ROOT = os.path.join(DATA_DIR, "static") + MEDIA_URL = "/media/" + MEDIA_ROOT = os.path.join(DATA_DIR, "media") + MEDIA_BASE_URL = values.Value(None, environ_name="MEDIA_BASE_URL", environ_prefix=None) + + SITE_ID = 1 + + STORAGES = { + "default": { + "BACKEND": "storages.backends.s3.S3Storage", + }, + "staticfiles": { + "BACKEND": values.Value( + "whitenoise.storage.CompressedManifestStaticFilesStorage", + environ_name="STORAGES_STATICFILES_BACKEND", + ), + }, + } + + # Media + AWS_S3_ENDPOINT_URL = values.Value(environ_name="AWS_S3_ENDPOINT_URL", environ_prefix=None) + AWS_S3_ACCESS_KEY_ID = values.Value(environ_name="AWS_S3_ACCESS_KEY_ID", environ_prefix=None) + AWS_S3_SECRET_ACCESS_KEY = values.Value( + environ_name="AWS_S3_SECRET_ACCESS_KEY", environ_prefix=None + ) + AWS_S3_REGION_NAME = values.Value(environ_name="AWS_S3_REGION_NAME", environ_prefix=None) + AWS_STORAGE_BUCKET_NAME = values.Value( + "conversations-media-storage", + environ_name="AWS_STORAGE_BUCKET_NAME", + environ_prefix=None, + ) + + # Internationalization + # https://docs.djangoproject.com/en/3.1/topics/i18n/ + + # Languages + LANGUAGE_CODE = values.Value("en-us") + # cookie & language is set from frontend + LANGUAGE_COOKIE_NAME = "conversation_language" + LANGUAGE_COOKIE_PATH = "/" + + DRF_NESTED_MULTIPART_PARSER = { + # output of parser is converted to querydict + # if is set to False, dict python is returned + "querydict": False, + } + + # Careful! Languages should be ordered by priority, as this tuple is used to get + # fallback/default languages throughout the app. + LANGUAGES = values.SingleNestedTupleValue( + ( + ("en-us", "English"), + ("fr-fr", "Franรงais"), + ("de-de", "Deutsch"), + ("nl-nl", "Nederlands"), + ("es-es", "Espaรฑol"), + ) + ) + + LOCALE_PATHS = (os.path.join(BASE_DIR, "locale"),) + + TIME_ZONE = "UTC" + USE_I18N = True + USE_TZ = True + + # Templates + TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [os.path.join(BASE_DIR, "templates")], + "OPTIONS": { + "context_processors": [ + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + "django.template.context_processors.csrf", + "django.template.context_processors.debug", + "django.template.context_processors.i18n", + "django.template.context_processors.media", + "django.template.context_processors.request", + "django.template.context_processors.tz", + ], + "loaders": [ + "django.template.loaders.filesystem.Loader", + "django.template.loaders.app_directories.Loader", + ], + }, + }, + ] + + MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "whitenoise.middleware.WhiteNoiseMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.locale.LocaleMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", + "corsheaders.middleware.CorsMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "dockerflow.django.middleware.DockerflowMiddleware", + ] + + AUTHENTICATION_BACKENDS = [ + "django.contrib.auth.backends.ModelBackend", + "core.authentication.backends.OIDCAuthenticationBackend", + ] + + # Django applications from the highest priority to the lowest + INSTALLED_APPS = [ + "chat", + "core", + "demo", + "drf_spectacular", + # Third party apps + "corsheaders", + "django_filters", + "dockerflow.django", + "rest_framework", + "parler", + "easy_thumbnails", + # Django + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.postgres", + "django.contrib.sessions", + "django.contrib.sites", + "django.contrib.messages", + "django.contrib.staticfiles", + # OIDC third party + "mozilla_django_oidc", + ] + + # Cache + CACHES = { + "default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"}, + } + + REST_FRAMEWORK = { + "DEFAULT_AUTHENTICATION_CLASSES": ( + "mozilla_django_oidc.contrib.drf.OIDCAuthentication", + "rest_framework.authentication.SessionAuthentication", + ), + "DEFAULT_PARSER_CLASSES": [ + "rest_framework.parsers.JSONParser", + "nested_multipart_parser.drf.DrfNestedParser", + ], + "DEFAULT_RENDERER_CLASSES": [ + # ๐Ÿ”’๏ธ Disable BrowsableAPIRenderer which provides forms allowing a user to + # see all the data in the database (ie a serializer with a ForeignKey field + # will generate a form with a field with all possible values of the FK). + "rest_framework.renderers.JSONRenderer", + ], + "EXCEPTION_HANDLER": "core.api.exception_handler", + "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", + "PAGE_SIZE": 20, + "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning", + "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", + "DEFAULT_THROTTLE_RATES": { + "user_list_sustained": values.Value( + default="180/hour", + environ_name="API_USERS_LIST_THROTTLE_RATE_SUSTAINED", + environ_prefix=None, + ), + "user_list_burst": values.Value( + default="30/minute", + environ_name="API_USERS_LIST_THROTTLE_RATE_BURST", + environ_prefix=None, + ), + }, + } + + SPECTACULAR_SETTINGS = { + "TITLE": "Conversations API", + "DESCRIPTION": "This is the conversations API schema.", + "VERSION": "1.0.0", + "SERVE_INCLUDE_SCHEMA": False, + "ENABLE_DJANGO_DEPLOY_CHECK": values.BooleanValue( + default=False, + environ_name="SPECTACULAR_SETTINGS_ENABLE_DJANGO_DEPLOY_CHECK", + ), + "COMPONENT_SPLIT_REQUEST": True, + # OTHER SETTINGS + "SWAGGER_UI_DIST": "SIDECAR", # shorthand to use the sidecar instead + "SWAGGER_UI_FAVICON_HREF": "SIDECAR", + "REDOC_DIST": "SIDECAR", + } + + # Mail + EMAIL_BACKEND = values.Value("django.core.mail.backends.smtp.EmailBackend") + EMAIL_BRAND_NAME = values.Value(None) + EMAIL_HOST = values.Value(None) + EMAIL_HOST_USER = values.Value(None) + EMAIL_HOST_PASSWORD = values.Value(None) + EMAIL_LOGO_IMG = values.Value(None) + EMAIL_PORT = values.PositiveIntegerValue(None) + EMAIL_USE_TLS = values.BooleanValue(False) + EMAIL_USE_SSL = values.BooleanValue(False) + EMAIL_FROM = values.Value("from@example.com") + + AUTH_USER_MODEL = "core.User" + + # CORS + CORS_ALLOW_CREDENTIALS = True + CORS_ALLOW_ALL_ORIGINS = values.BooleanValue(False) + CORS_ALLOWED_ORIGINS = values.ListValue([]) + CORS_ALLOWED_ORIGIN_REGEXES = values.ListValue([]) + + # Sentry + SENTRY_DSN = values.Value(None, environ_name="SENTRY_DSN", environ_prefix=None) + + # Frontend + FRONTEND_THEME = values.Value(None, environ_name="FRONTEND_THEME", environ_prefix=None) + FRONTEND_HOMEPAGE_FEATURE_ENABLED = values.BooleanValue( + default=True, + environ_name="FRONTEND_HOMEPAGE_FEATURE_ENABLED", + environ_prefix=None, + ) + FRONTEND_CSS_URL = values.Value(None, environ_name="FRONTEND_CSS_URL", environ_prefix=None) + + THEME_CUSTOMIZATION_FILE_PATH = values.Value( + os.path.join(BASE_DIR, "conversations/configuration/theme/default.json"), + environ_name="THEME_CUSTOMIZATION_FILE_PATH", + environ_prefix=None, + ) + + THEME_CUSTOMIZATION_CACHE_TIMEOUT = values.Value( + 60 * 60 * 24, + environ_name="THEME_CUSTOMIZATION_CACHE_TIMEOUT", + environ_prefix=None, + ) + + # Posthog + POSTHOG_KEY = values.DictValue(None, environ_name="POSTHOG_KEY", environ_prefix=None) + + # Crisp + CRISP_WEBSITE_ID = values.Value(None, environ_name="CRISP_WEBSITE_ID", environ_prefix=None) + + # Easy thumbnails + THUMBNAIL_EXTENSION = "webp" + THUMBNAIL_TRANSPARENCY_EXTENSION = "webp" + THUMBNAIL_DEFAULT_STORAGE_ALIAS = "default" + THUMBNAIL_ALIASES = {} + + # Session + SESSION_ENGINE = "django.contrib.sessions.backends.cache" + SESSION_CACHE_ALIAS = "default" + SESSION_COOKIE_AGE = values.PositiveIntegerValue( + default=60 * 60 * 12, environ_name="SESSION_COOKIE_AGE", environ_prefix=None + ) + + # OIDC - Authorization Code Flow + OIDC_CREATE_USER = values.BooleanValue( + default=True, + environ_name="OIDC_CREATE_USER", + ) + OIDC_RP_SIGN_ALGO = values.Value("RS256", environ_name="OIDC_RP_SIGN_ALGO", environ_prefix=None) + OIDC_RP_CLIENT_ID = values.Value( + "conversations", environ_name="OIDC_RP_CLIENT_ID", environ_prefix=None + ) + OIDC_RP_CLIENT_SECRET = values.Value( + None, + environ_name="OIDC_RP_CLIENT_SECRET", + environ_prefix=None, + ) + OIDC_OP_JWKS_ENDPOINT = values.Value(environ_name="OIDC_OP_JWKS_ENDPOINT", environ_prefix=None) + OIDC_OP_AUTHORIZATION_ENDPOINT = values.Value( + environ_name="OIDC_OP_AUTHORIZATION_ENDPOINT", environ_prefix=None + ) + OIDC_OP_TOKEN_ENDPOINT = values.Value( + None, environ_name="OIDC_OP_TOKEN_ENDPOINT", environ_prefix=None + ) + OIDC_OP_USER_ENDPOINT = values.Value( + None, environ_name="OIDC_OP_USER_ENDPOINT", environ_prefix=None + ) + OIDC_OP_LOGOUT_ENDPOINT = values.Value( + None, environ_name="OIDC_OP_LOGOUT_ENDPOINT", environ_prefix=None + ) + OIDC_AUTH_REQUEST_EXTRA_PARAMS = values.DictValue( + {}, environ_name="OIDC_AUTH_REQUEST_EXTRA_PARAMS", environ_prefix=None + ) + OIDC_RP_SCOPES = values.Value( + "openid email", environ_name="OIDC_RP_SCOPES", environ_prefix=None + ) + LOGIN_REDIRECT_URL = values.Value(None, environ_name="LOGIN_REDIRECT_URL", environ_prefix=None) + LOGIN_REDIRECT_URL_FAILURE = values.Value( + None, environ_name="LOGIN_REDIRECT_URL_FAILURE", environ_prefix=None + ) + LOGOUT_REDIRECT_URL = values.Value( + None, environ_name="LOGOUT_REDIRECT_URL", environ_prefix=None + ) + OIDC_USE_NONCE = values.BooleanValue( + default=True, environ_name="OIDC_USE_NONCE", environ_prefix=None + ) + OIDC_REDIRECT_REQUIRE_HTTPS = values.BooleanValue( + default=False, environ_name="OIDC_REDIRECT_REQUIRE_HTTPS", environ_prefix=None + ) + OIDC_REDIRECT_ALLOWED_HOSTS = values.ListValue( + default=[], environ_name="OIDC_REDIRECT_ALLOWED_HOSTS", environ_prefix=None + ) + OIDC_STORE_ID_TOKEN = values.BooleanValue( + default=True, environ_name="OIDC_STORE_ID_TOKEN", environ_prefix=None + ) + OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION = values.BooleanValue( + default=True, + environ_name="OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION", + environ_prefix=None, + ) + OIDC_USE_PKCE = values.BooleanValue( + default=False, environ_name="OIDC_USE_PKCE", environ_prefix=None + ) + OIDC_PKCE_CODE_CHALLENGE_METHOD = values.Value( + default="S256", + environ_name="OIDC_PKCE_CODE_CHALLENGE_METHOD", + environ_prefix=None, + ) + OIDC_PKCE_CODE_VERIFIER_SIZE = values.IntegerValue( + default=64, environ_name="OIDC_PKCE_CODE_VERIFIER_SIZE", environ_prefix=None + ) + OIDC_STORE_ACCESS_TOKEN = values.BooleanValue( + default=False, environ_name="OIDC_STORE_ACCESS_TOKEN", environ_prefix=None + ) + OIDC_STORE_REFRESH_TOKEN = values.BooleanValue( + default=False, environ_name="OIDC_STORE_REFRESH_TOKEN", environ_prefix=None + ) + OIDC_STORE_REFRESH_TOKEN_KEY = values.Value( + default=None, + environ_name="OIDC_STORE_REFRESH_TOKEN_KEY", + environ_prefix=None, + ) + + # WARNING: Enabling this setting allows multiple user accounts to share the same email + # address. This may cause security issues and is not recommended for production use when + # email is activated as fallback for identification (see previous setting). + OIDC_ALLOW_DUPLICATE_EMAILS = values.BooleanValue( + default=False, + environ_name="OIDC_ALLOW_DUPLICATE_EMAILS", + environ_prefix=None, + ) + + USER_OIDC_ESSENTIAL_CLAIMS = values.ListValue( + default=[], environ_name="USER_OIDC_ESSENTIAL_CLAIMS", environ_prefix=None + ) + + OIDC_USERINFO_FULLNAME_FIELDS = values.ListValue( + default=["first_name", "last_name"], + environ_name="OIDC_USERINFO_FULLNAME_FIELDS", + environ_prefix=None, + ) + OIDC_USERINFO_SHORTNAME_FIELD = values.Value( + default="first_name", + environ_name="OIDC_USERINFO_SHORTNAME_FIELD", + environ_prefix=None, + ) + + ALLOW_LOGOUT_GET_METHOD = values.BooleanValue( + default=True, environ_name="ALLOW_LOGOUT_GET_METHOD", environ_prefix=None + ) + + # AI service + AI_API_KEY = values.Value(None, environ_name="AI_API_KEY", environ_prefix=None) + AI_BASE_URL = values.Value( + os.environ.get("AI_RUNNER_URL", None), # default on the Docker environment variable + environ_name="AI_BASE_URL", + environ_prefix=None, + ) + AI_MODEL = values.Value( + os.environ.get("AI_RUNNER_MODEL", None), + environ_name="AI_MODEL", + environ_prefix=None, + ) + AI_AGENT_NAME = values.Value( + "Conversations Assistant", + environ_name="AI_AGENT_NAME", + environ_prefix=None, + ) + AI_AGENT_INSTRUCTIONS = values.Value( + "You are a helpful assistant", + environ_name="AI_AGENT_INSTRUCTIONS", + environ_prefix=None, + ) + + # Logging + # We want to make it easy to log to console but by default we log production + # to Sentry and don't want to log to console. + LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "simple": { + "format": "{asctime} {name} {levelname} {message}", + "style": "{", + }, + }, + "handlers": { + "console": { + "class": "logging.StreamHandler", + "formatter": "simple", + }, + }, + # Override root logger to send it to console + "root": { + "handlers": ["console"], + "level": values.Value( + "INFO", environ_name="LOGGING_LEVEL_LOGGERS_ROOT", environ_prefix=None + ), + }, + "loggers": { + "core": { + "handlers": ["console"], + "level": values.Value( + "INFO", + environ_name="LOGGING_LEVEL_LOGGERS_APP", + environ_prefix=None, + ), + "propagate": False, + }, + "docs.security": { + "handlers": ["console"], + "level": values.Value( + "INFO", + environ_name="LOGGING_LEVEL_LOGGERS_SECURITY", + environ_prefix=None, + ), + "propagate": False, + }, + }, + } + + API_USERS_LIST_LIMIT = values.PositiveIntegerValue( + default=5, + environ_name="API_USERS_LIST_LIMIT", + environ_prefix=None, + ) + + # ML Flow + ML_FLOW_TRACKING_URI = values.Value( + None, + environ_name="ML_FLOW_TRACKING_URI", + environ_prefix=None, + ) + ML_FLOW_EXPERIMENT_NAME = values.Value( + "conversations-openai-tracing", + environ_name="ML_FLOW_EXPERIMENT_NAME", + environ_prefix=None, + ) + + # pylint: disable=invalid-name + @property + def ENVIRONMENT(self): + """Environment in which the application is launched.""" + return self.__class__.__name__.lower() + + # pylint: disable=invalid-name + @property + def RELEASE(self): + """ + Return the release information. + + Delegate to the module function to enable easier testing. + """ + return get_release() + + # pylint: disable=invalid-name + @property + def PARLER_LANGUAGES(self): + """ + Return languages for Parler computed from the LANGUAGES and LANGUAGE_CODE settings. + """ + return { + self.SITE_ID: tuple({"code": code} for code, _name in self.LANGUAGES), + "default": { + "fallbacks": [self.LANGUAGE_CODE], + "hide_untranslated": False, + }, + } + + @classmethod + def post_setup(cls): + """Post setup configuration. + This is the place where you can configure settings that require other + settings to be loaded. + """ + super().post_setup() + + # The SENTRY_DSN setting should be available to activate sentry for an environment + if cls.SENTRY_DSN is not None: + sentry_sdk.init( + dsn=cls.SENTRY_DSN, + environment=cls.__name__.lower(), + release=get_release(), + integrations=[DjangoIntegration()], + ) + sentry_sdk.set_tag("application", "backend") + + # Ignore the logs added by the DockerflowMiddleware + ignore_logger("request.summary") + + if cls.OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION and cls.OIDC_ALLOW_DUPLICATE_EMAILS: + raise ValueError( + "Both OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION and " + "OIDC_ALLOW_DUPLICATE_EMAILS cannot be set to True simultaneously. " + ) + + +class Build(Base): + """Settings used when the application is built. + + This environment should not be used to run the application. Just to build it with non-blocking + settings. + """ + + SECRET_KEY = values.Value("DummyKey") + STORAGES = { + "default": { + "BACKEND": "django.core.files.storage.FileSystemStorage", + }, + "staticfiles": { + "BACKEND": values.Value( + "whitenoise.storage.CompressedManifestStaticFilesStorage", + environ_name="STORAGES_STATICFILES_BACKEND", + ), + }, + } + + +class Development(Base): + """ + Development environment settings + + We set DEBUG to True and configure the server to respond from all hosts. + """ + + ALLOWED_HOSTS = ["*"] + CORS_ALLOW_ALL_ORIGINS = True + CSRF_TRUSTED_ORIGINS = ["http://localhost:8072", "http://localhost:3000"] + DEBUG = True + + SESSION_COOKIE_NAME = "conversations_sessionid" + + USE_SWAGGER = True + SESSION_CACHE_ALIAS = "session" + CACHES = { + "default": { + "BACKEND": "django.core.cache.backends.dummy.DummyCache", + }, + "session": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": values.Value( + "redis://redis:6379/2", + environ_name="REDIS_URL", + environ_prefix=None, + ), + "TIMEOUT": values.IntegerValue( + 30, # timeout in seconds + environ_name="CACHES_DEFAULT_TIMEOUT", + environ_prefix=None, + ), + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + }, + }, + } + + def __init__(self): + # pylint: disable=invalid-name + self.INSTALLED_APPS += ["django_extensions", "drf_spectacular_sidecar"] + + +class Test(Base): + """Test environment settings""" + + PASSWORD_HASHERS = [ + "django.contrib.auth.hashers.MD5PasswordHasher", + ] + USE_SWAGGER = True + # Static files are not used in the test environment + # Tests are raising warnings because the /data/static directory does not exist + STATIC_ROOT = None + + os.environ["OPENAI_AGENTS_DISABLE_TRACING"] = "true" + + def __init__(self): + # pylint: disable=invalid-name + self.INSTALLED_APPS += ["drf_spectacular_sidecar"] + + +class ContinuousIntegration(Test): + """ + Continuous Integration environment settings + + nota bene: it should inherit from the Test environment. + """ + + +class Production(Base): + """ + Production environment settings + + You must define the ALLOWED_HOSTS environment variable in Production + configuration (and derived configurations): + ALLOWED_HOSTS=["foo.com", "foo.fr"] + """ + + # Security + # Add allowed host from environment variables. + # The machine hostname is added by default, + # it makes the application pingable by a load balancer on the same machine by example + ALLOWED_HOSTS = [ + *values.ListValue([], environ_name="ALLOWED_HOSTS"), + gethostbyname(gethostname()), + ] + CSRF_TRUSTED_ORIGINS = values.ListValue([]) + SECURE_BROWSER_XSS_FILTER = True + SECURE_CONTENT_TYPE_NOSNIFF = True + + # SECURE_PROXY_SSL_HEADER allows to fix the scheme in Django's HttpRequest + # object when your application is behind a reverse proxy. + # + # Keep this SECURE_PROXY_SSL_HEADER configuration only if : + # - your Django app is behind a proxy. + # - your proxy strips the X-Forwarded-Proto header from all incoming requests + # - Your proxy sets the X-Forwarded-Proto header and sends it to Django + # + # In other cases, you should comment the following line to avoid security issues. + # SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") + SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") + SECURE_HSTS_SECONDS = 60 + SECURE_HSTS_PRELOAD = True + SECURE_HSTS_INCLUDE_SUBDOMAINS = True + SECURE_SSL_REDIRECT = True + SECURE_REDIRECT_EXEMPT = [ + "^__lbheartbeat__", + "^__heartbeat__", + ] + + # Modern browsers require to have the `secure` attribute on cookies with `Samesite=none` + CSRF_COOKIE_SECURE = True + SESSION_COOKIE_SECURE = True + + # Privacy + SECURE_REFERRER_POLICY = "same-origin" + + CACHES = { + "default": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": values.Value( + "redis://redis:6379/1", + environ_name="REDIS_URL", + environ_prefix=None, + ), + "TIMEOUT": values.IntegerValue( + 30, # timeout in seconds + environ_name="CACHES_DEFAULT_TIMEOUT", + environ_prefix=None, + ), + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + }, + "KEY_PREFIX": values.Value( + "conversations", + environ_name="CACHES_KEY_PREFIX", + environ_prefix=None, + ), + }, + } + + +class Feature(Production): + """ + Feature environment settings + + nota bene: it should inherit from the Production environment. + """ + + +class Staging(Production): + """ + Staging environment settings + + nota bene: it should inherit from the Production environment. + """ + + +class PreProduction(Production): + """ + Pre-production environment settings + + nota bene: it should inherit from the Production environment. + """ + + +class Demo(Production): + """ + Demonstration environment settings + + nota bene: it should inherit from the Production environment. + """ + + STORAGES = { + "default": { + "BACKEND": "django.core.files.storage.FileSystemStorage", + }, + "staticfiles": { + "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage", + }, + } diff --git a/src/backend/conversations/urls.py b/src/backend/conversations/urls.py new file mode 100644 index 0000000..736a26a --- /dev/null +++ b/src/backend/conversations/urls.py @@ -0,0 +1,48 @@ +"""URL configuration for the conversations project""" + +from django.conf import settings +from django.conf.urls.static import static +from django.contrib import admin +from django.contrib.staticfiles.urls import staticfiles_urlpatterns +from django.urls import include, path, re_path + +from drf_spectacular.views import ( + SpectacularJSONAPIView, + SpectacularRedocView, + SpectacularSwaggerView, +) + +urlpatterns = [ + path("admin/", admin.site.urls), + path("", include("core.urls")), +] + +if settings.DEBUG: + urlpatterns = ( + urlpatterns + + staticfiles_urlpatterns() + + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + ) + + +if settings.USE_SWAGGER or settings.DEBUG: + urlpatterns += [ + path( + f"api/{settings.API_VERSION}/swagger.json", + SpectacularJSONAPIView.as_view( + api_version=settings.API_VERSION, + urlconf="core.urls", + ), + name="client-api-schema", + ), + path( + f"api/{settings.API_VERSION}/swagger/", + SpectacularSwaggerView.as_view(url_name="client-api-schema"), + name="swagger-ui-schema", + ), + re_path( + f"api/{settings.API_VERSION}/redoc/", + SpectacularRedocView.as_view(url_name="client-api-schema"), + name="redoc-schema", + ), + ] diff --git a/src/backend/conversations/wsgi.py b/src/backend/conversations/wsgi.py new file mode 100644 index 0000000..3a803a1 --- /dev/null +++ b/src/backend/conversations/wsgi.py @@ -0,0 +1,17 @@ +""" +WSGI config for the conversations project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/ +""" + +import os + +from configurations.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "conversations.settings") +os.environ.setdefault("DJANGO_CONFIGURATION", "Development") + +application = get_wsgi_application() diff --git a/src/backend/core/__init__.py b/src/backend/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/backend/core/admin.py b/src/backend/core/admin.py new file mode 100644 index 0000000..1efc8ae --- /dev/null +++ b/src/backend/core/admin.py @@ -0,0 +1,93 @@ +"""Admin classes and registrations for core app.""" + +from django.contrib import admin +from django.contrib.auth import admin as auth_admin +from django.utils.translation import gettext_lazy as _ + +from . import models + + +@admin.register(models.User) +class UserAdmin(auth_admin.UserAdmin): + """Admin class for the User model""" + + fieldsets = ( + ( + None, + { + "fields": ( + "id", + "admin_email", + "password", + ) + }, + ), + ( + _("Personal info"), + { + "fields": ( + "sub", + "email", + "full_name", + "short_name", + "language", + "timezone", + ) + }, + ), + ( + _("Permissions"), + { + "fields": ( + "is_active", + "is_device", + "is_staff", + "is_superuser", + "groups", + "user_permissions", + ), + }, + ), + (_("Important dates"), {"fields": ("created_at", "updated_at")}), + ) + add_fieldsets = ( + ( + None, + { + "classes": ("wide",), + "fields": ("email", "password1", "password2"), + }, + ), + ) + list_display = ( + "id", + "sub", + "full_name", + "admin_email", + "email", + "is_active", + "is_staff", + "is_superuser", + "is_device", + "created_at", + "updated_at", + ) + list_filter = ("is_staff", "is_superuser", "is_device", "is_active") + ordering = ( + "is_active", + "-is_superuser", + "-is_staff", + "-is_device", + "-updated_at", + "full_name", + ) + readonly_fields = ( + "id", + "sub", + "email", + "full_name", + "short_name", + "created_at", + "updated_at", + ) + search_fields = ("id", "sub", "admin_email", "email", "full_name") diff --git a/src/backend/core/api/__init__.py b/src/backend/core/api/__init__.py new file mode 100644 index 0000000..c88af71 --- /dev/null +++ b/src/backend/core/api/__init__.py @@ -0,0 +1,41 @@ +"""Conversations core API endpoints""" + +from django.conf import settings +from django.core.exceptions import ValidationError + +from rest_framework import exceptions as drf_exceptions +from rest_framework import views as drf_views +from rest_framework.decorators import api_view +from rest_framework.response import Response + + +def exception_handler(exc, context): + """Handle Django ValidationError as an accepted exception. + + For the parameters, see ``exception_handler`` + This code comes from twidi's gist: + https://gist.github.com/twidi/9d55486c36b6a51bdcb05ce3a763e79f + """ + if isinstance(exc, ValidationError): + detail = None + if hasattr(exc, "message_dict"): + detail = exc.message_dict + elif hasattr(exc, "message"): + detail = exc.message + elif hasattr(exc, "messages"): + detail = exc.messages + + exc = drf_exceptions.ValidationError(detail=detail) + + return drf_views.exception_handler(exc, context) + + +# pylint: disable=unused-argument +@api_view(["GET"]) +def get_frontend_configuration(request): + """Returns the frontend configuration dict as configured in settings.""" + frontend_configuration = { + "LANGUAGE_CODE": settings.LANGUAGE_CODE, + } + frontend_configuration.update(settings.FRONTEND_CONFIGURATION) + return Response(frontend_configuration) diff --git a/src/backend/core/api/serializers.py b/src/backend/core/api/serializers.py new file mode 100644 index 0000000..418a4e0 --- /dev/null +++ b/src/backend/core/api/serializers.py @@ -0,0 +1,34 @@ +"""Client serializers for the conversations core app.""" + +from rest_framework import serializers + +from core import models + + +class UserSerializer(serializers.ModelSerializer): + """Serialize users.""" + + class Meta: + model = models.User + fields = ["id", "email", "full_name", "short_name", "language"] + read_only_fields = ["id", "email", "full_name", "short_name"] + + +class UserLightSerializer(UserSerializer): + """Serialize users with limited fields.""" + + id = serializers.SerializerMethodField(read_only=True) + email = serializers.SerializerMethodField(read_only=True) + + def get_id(self, _user): + """Return always None. Here to have the same fields than in UserSerializer.""" + return None + + def get_email(self, _user): + """Return always None. Here to have the same fields than in UserSerializer.""" + return None + + class Meta: + model = models.User + fields = ["id", "email", "full_name", "short_name"] + read_only_fields = ["id", "email", "full_name", "short_name"] diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py new file mode 100644 index 0000000..36ba1b0 --- /dev/null +++ b/src/backend/core/api/viewsets.py @@ -0,0 +1,250 @@ +"""API endpoints""" + +import json +import logging + +from django.conf import settings +from django.contrib.postgres.search import TrigramSimilarity +from django.core.cache import cache +from django.db.models.expressions import RawSQL +from django.utils.text import slugify + +import rest_framework as drf +from rest_framework import viewsets +from rest_framework.permissions import AllowAny +from rest_framework.throttling import UserRateThrottle + +from core import models, permissions + +from . import serializers + +logger = logging.getLogger(__name__) + +# pylint: disable=too-many-ancestors + + +class NestedGenericViewSet(viewsets.GenericViewSet): + """ + A generic Viewset aims to be used in a nested route context. + e.g: `/api/v1.0/resource_1//resource_2//` + + It allows to define all url kwargs and lookup fields to perform the lookup. + """ + + lookup_fields: list[str] = ["pk"] + lookup_url_kwargs: list[str] = [] + + def __getattribute__(self, item): + """ + This method is overridden to allow to get the last lookup field or lookup url kwarg + when accessing the `lookup_field` or `lookup_url_kwarg` attribute. This is useful + to keep compatibility with all methods used by the parent class `GenericViewSet`. + """ + if item in ["lookup_field", "lookup_url_kwarg"]: + return getattr(self, item + "s", [None])[-1] + + return super().__getattribute__(item) + + def get_queryset(self): + """ + Get the list of items for this view. + + `lookup_fields` attribute is enumerated here to perform the nested lookup. + """ + queryset = super().get_queryset() + + # The last lookup field is removed to perform the nested lookup as it corresponds + # to the object pk, it is used within get_object method. + lookup_url_kwargs = ( + self.lookup_url_kwargs[:-1] if self.lookup_url_kwargs else self.lookup_fields[:-1] + ) + + filter_kwargs = {} + for index, lookup_url_kwarg in enumerate(lookup_url_kwargs): + if lookup_url_kwarg not in self.kwargs: + raise KeyError( + f"Expected view {self.__class__.__name__} to be called with a URL " + f'keyword argument named "{lookup_url_kwarg}". Fix your URL conf, or ' + "set the `.lookup_fields` attribute on the view correctly." + ) + + filter_kwargs.update({self.lookup_fields[index]: self.kwargs[lookup_url_kwarg]}) + + return queryset.filter(**filter_kwargs) + + +class SerializerPerActionMixin: + """ + A mixin to allow to define serializer classes for each action. + + This mixin is useful to avoid to define a serializer class for each action in the + `get_serializer_class` method. + + Example: + ``` + class MyViewSet(SerializerPerActionMixin, viewsets.GenericViewSet): + serializer_class = MySerializer + list_serializer_class = MyListSerializer + retrieve_serializer_class = MyRetrieveSerializer + ``` + """ + + def get_serializer_class(self): + """ + Return the serializer class to use depending on the action. + """ + if serializer_class := getattr(self, f"{self.action}_serializer_class", None): + return serializer_class + return super().get_serializer_class() + + +class Pagination(drf.pagination.PageNumberPagination): + """Pagination to display no more than 100 objects per page sorted by creation date.""" + + ordering = "-created_on" + max_page_size = 200 + page_size_query_param = "page_size" + + +class UserListThrottleBurst(UserRateThrottle): + """Throttle for the user list endpoint.""" + + scope = "user_list_burst" + + +class UserListThrottleSustained(UserRateThrottle): + """Throttle for the user list endpoint.""" + + scope = "user_list_sustained" + + +class UserViewSet(drf.mixins.UpdateModelMixin, viewsets.GenericViewSet, drf.mixins.ListModelMixin): + """User ViewSet""" + + permission_classes = [permissions.IsSelf] + queryset = models.User.objects.filter(is_active=True) + serializer_class = serializers.UserSerializer + pagination_class = None + throttle_classes = [] + + def get_throttles(self): + self.throttle_classes = [] + if self.action == "list": + self.throttle_classes = [UserListThrottleBurst, UserListThrottleSustained] + + return super().get_throttles() + + def get_queryset(self): + """ + Limit listed users by querying the email field with a trigram similarity + search if a query is provided. + Limit listed users by excluding users already in the document if a document_id + is provided. + """ + queryset = self.queryset + + if self.action != "list": + return queryset + + # Exclude all users already in the given document + if document_id := self.request.query_params.get("document_id", ""): + queryset = queryset.exclude(documentaccess__document_id=document_id) + + if not (query := self.request.query_params.get("q", "")) or len(query) < 5: + return queryset.none() + + # For emails, match emails by Levenstein distance to prevent typing errors + if "@" in query: + return ( + queryset.annotate(distance=RawSQL("levenshtein(email::text, %s::text)", (query,))) + .filter(distance__lte=3) + .order_by("distance", "email")[: settings.API_USERS_LIST_LIMIT] + ) + + # Use trigram similarity for non-email-like queries + # For performance reasons we filter first by similarity, which relies on an + # index, then only calculate precise similarity scores for sorting purposes + return ( + queryset.filter(email__trigram_word_similar=query) + .annotate(similarity=TrigramSimilarity("email", query)) + .filter(similarity__gt=0.2) + .order_by("-similarity", "email")[: settings.API_USERS_LIST_LIMIT] + ) + + @drf.decorators.action( + detail=False, + methods=["get"], + url_name="me", + url_path="me", + permission_classes=[permissions.IsAuthenticated], + ) + def get_me(self, request): + """ + Return information on currently logged user + """ + context = {"request": request} + + return drf.response.Response(self.serializer_class(request.user, context=context).data) + + +class ConfigView(drf.views.APIView): + """API ViewSet for sharing some public settings.""" + + permission_classes = [AllowAny] + + def get(self, request): + """ + GET /api/v1.0/config/ + Return a dictionary of public settings. + """ + array_settings = [ + "CRISP_WEBSITE_ID", + "ENVIRONMENT", + "FRONTEND_CSS_URL", + "FRONTEND_HOMEPAGE_FEATURE_ENABLED", + "FRONTEND_THEME", + "MEDIA_BASE_URL", + "POSTHOG_KEY", + "LANGUAGES", + "LANGUAGE_CODE", + "SENTRY_DSN", + ] + dict_settings = {} + for setting in array_settings: + if hasattr(settings, setting): + dict_settings[setting] = getattr(settings, setting) + + dict_settings["theme_customization"] = self._load_theme_customization() + + return drf.response.Response(dict_settings) + + def _load_theme_customization(self): + if not settings.THEME_CUSTOMIZATION_FILE_PATH: + return {} + + cache_key = f"theme_customization_{slugify(settings.THEME_CUSTOMIZATION_FILE_PATH)}" + theme_customization = cache.get(cache_key, {}) + if theme_customization: + return theme_customization + + try: + with open(settings.THEME_CUSTOMIZATION_FILE_PATH, "r", encoding="utf-8") as f: + theme_customization = json.load(f) + except FileNotFoundError: + logger.error( + "Configuration file not found: %s", + settings.THEME_CUSTOMIZATION_FILE_PATH, + ) + except json.JSONDecodeError: + logger.error( + "Configuration file is not a valid JSON: %s", + settings.THEME_CUSTOMIZATION_FILE_PATH, + ) + else: + cache.set( + cache_key, + theme_customization, + settings.THEME_CUSTOMIZATION_CACHE_TIMEOUT, + ) + + return theme_customization diff --git a/src/backend/core/apps.py b/src/backend/core/apps.py new file mode 100644 index 0000000..e05bd1d --- /dev/null +++ b/src/backend/core/apps.py @@ -0,0 +1,11 @@ +"""Conversations Core application""" +# from django.apps import AppConfig +# from django.utils.translation import gettext_lazy as _ + + +# class CoreConfig(AppConfig): +# """Configuration class for the conversations core app.""" + +# name = "core" +# app_label = "core" +# verbose_name = _("conversations core application") diff --git a/src/backend/core/authentication/__init__.py b/src/backend/core/authentication/__init__.py new file mode 100644 index 0000000..977382d --- /dev/null +++ b/src/backend/core/authentication/__init__.py @@ -0,0 +1,52 @@ +"""Custom authentication classes for the Conversations core app""" + +from django.conf import settings + +from rest_framework.authentication import BaseAuthentication +from rest_framework.exceptions import AuthenticationFailed + + +class ServerToServerAuthentication(BaseAuthentication): + """ + Custom authentication class for server-to-server requests. + Validates the presence and correctness of the Authorization header. + """ + + AUTH_HEADER = "Authorization" + TOKEN_TYPE = "Bearer" # noqa S105 + + def authenticate(self, request): + """ + Authenticate the server-to-server request by validating the Authorization header. + + This method checks if the Authorization header is present in the request, ensures it + contains a valid token with the correct format, and verifies the token against the + list of allowed server-to-server tokens. If the header is missing, improperly formatted, + or contains an invalid token, an AuthenticationFailed exception is raised. + + Returns: + None: If authentication is successful + (no user is authenticated for server-to-server requests). + + Raises: + AuthenticationFailed: If the Authorization header is missing, malformed, + or contains an invalid token. + """ + auth_header = request.headers.get(self.AUTH_HEADER) + if not auth_header: + raise AuthenticationFailed("Authorization header is missing.") + + # Validate token format and existence + auth_parts = auth_header.split(" ") + if len(auth_parts) != 2 or auth_parts[0] != self.TOKEN_TYPE: + raise AuthenticationFailed("Invalid authorization header.") + + token = auth_parts[1] + if token not in settings.SERVER_TO_SERVER_API_TOKENS: + raise AuthenticationFailed("Invalid server-to-server token.") + + # Authentication is successful, but no user is authenticated + + def authenticate_header(self, request): + """Return the WWW-Authenticate header value.""" + return f"{self.TOKEN_TYPE} realm='Create document server to server'" diff --git a/src/backend/core/authentication/backends.py b/src/backend/core/authentication/backends.py new file mode 100644 index 0000000..72b5933 --- /dev/null +++ b/src/backend/core/authentication/backends.py @@ -0,0 +1,59 @@ +"""Authentication Backends for the Conversations core app.""" + +import logging +import os + +from django.conf import settings +from django.core.exceptions import SuspiciousOperation + +from lasuite.oidc_login.backends import ( + OIDCAuthenticationBackend as LaSuiteOIDCAuthenticationBackend, +) + +from core.models import DuplicateEmailError + +logger = logging.getLogger(__name__) + +# Settings renamed warnings +if os.environ.get("USER_OIDC_FIELDS_TO_FULLNAME"): + logger.warning( + "USER_OIDC_FIELDS_TO_FULLNAME has been renamed " + "to OIDC_USERINFO_FULLNAME_FIELDS please update your settings." + ) + +if os.environ.get("USER_OIDC_FIELD_TO_SHORTNAME"): + logger.warning( + "USER_OIDC_FIELD_TO_SHORTNAME has been renamed " + "to OIDC_USERINFO_SHORTNAME_FIELD please update your settings." + ) + + +class OIDCAuthenticationBackend(LaSuiteOIDCAuthenticationBackend): + """Custom OpenID Connect (OIDC) Authentication Backend. + + This class overrides the default OIDC Authentication Backend to accommodate differences + in the User and Identity models, and handles signed and/or encrypted UserInfo response. + """ + + def get_extra_claims(self, user_info): + """ + Return extra claims from user_info. + + Args: + user_info (dict): The user information dictionary. + + Returns: + dict: A dictionary of extra claims. + """ + return { + "full_name": self.compute_full_name(user_info), + "short_name": user_info.get(settings.OIDC_USERINFO_SHORTNAME_FIELD), + } + + def get_existing_user(self, sub, email): + """Fetch existing user by sub or email.""" + + try: + return self.UserModel.objects.get_user_by_sub_or_email(sub, email) + except DuplicateEmailError as err: + raise SuspiciousOperation(err.message) from err diff --git a/src/backend/core/enums.py b/src/backend/core/enums.py new file mode 100644 index 0000000..e2ea4ba --- /dev/null +++ b/src/backend/core/enums.py @@ -0,0 +1,24 @@ +"""Core application enums declaration.""" + +import re + +from django.conf import global_settings, settings +from django.utils.translation import gettext_lazy as _ + +ATTACHMENTS_FOLDER = "attachments" +UUID_REGEX = r"[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}" +FILE_EXT_REGEX = r"\.[a-zA-Z0-9]{1,10}" +MEDIA_STORAGE_URL_PATTERN = re.compile( + f"{settings.MEDIA_URL:s}(?P{UUID_REGEX:s})/" + f"(?P{ATTACHMENTS_FOLDER:s}/{UUID_REGEX:s}(?:-unsafe)?{FILE_EXT_REGEX:s})$" +) +MEDIA_STORAGE_URL_EXTRACT = re.compile( + f"{settings.MEDIA_URL:s}({UUID_REGEX}/{ATTACHMENTS_FOLDER}/{UUID_REGEX}{FILE_EXT_REGEX})" +) + + +# In Django's code base, `LANGUAGES` is set by default with all supported languages. +# We can use it for the choice of languages which should not be limited to the few languages +# active in the app. +# pylint: disable=no-member +ALL_LANGUAGES = {language: _(name) for language, name in global_settings.LANGUAGES} diff --git a/src/backend/core/factories.py b/src/backend/core/factories.py new file mode 100644 index 0000000..860abe9 --- /dev/null +++ b/src/backend/core/factories.py @@ -0,0 +1,30 @@ +# ruff: noqa: S311 +""" +Core application factories +""" + +from django.conf import settings +from django.contrib.auth.hashers import make_password + +import factory.fuzzy +from faker import Faker + +from core import models + +fake = Faker() + + +class UserFactory(factory.django.DjangoModelFactory): + """A factory to random users for testing purposes.""" + + class Meta: + model = models.User + # Skip postgeneration save, no save is made in the postgeneration methods. + skip_postgeneration_save = True + + sub = factory.Sequence(lambda n: f"user{n!s}") + email = factory.Faker("email") + full_name = factory.Faker("name") + short_name = factory.Faker("first_name") + language = factory.fuzzy.FuzzyChoice([lang[0] for lang in settings.LANGUAGES]) + password = make_password("password") diff --git a/src/backend/core/fields.py b/src/backend/core/fields.py new file mode 100644 index 0000000..1125622 --- /dev/null +++ b/src/backend/core/fields.py @@ -0,0 +1,25 @@ +"""A JSONField for DRF to handle serialization/deserialization.""" + +import json + +from rest_framework import serializers + + +class JSONField(serializers.Field): + """ + A custom field for handling JSON data. + """ + + def to_representation(self, value): + """ + Convert the JSON string to a Python dictionary for serialization. + """ + return value + + def to_internal_value(self, data): + """ + Convert the Python dictionary to a JSON string for deserialization. + """ + if data is None: + return None + return json.dumps(data) diff --git a/src/backend/core/filters.py b/src/backend/core/filters.py new file mode 100644 index 0000000..bf7ac87 --- /dev/null +++ b/src/backend/core/filters.py @@ -0,0 +1,32 @@ +"""API filters for Conversations' core application.""" + +import unicodedata + +import django_filters + + +def remove_accents(value): + """Remove accents from a string (vรฉlo -> velo).""" + return "".join( + c for c in unicodedata.normalize("NFD", value) if unicodedata.category(c) != "Mn" + ) + + +class AccentInsensitiveCharFilter(django_filters.CharFilter): + """ + A custom CharFilter that filters on the accent-insensitive value searched. + """ + + def filter(self, qs, value): + """ + Apply the filter to the queryset using the unaccented version of the field. + + Args: + qs: The queryset to filter. + value: The value to search for in the unaccented field. + Returns: + A filtered queryset. + """ + if value: + value = remove_accents(value) + return super().filter(qs, value) diff --git a/src/backend/core/migrations/0001_initial.py b/src/backend/core/migrations/0001_initial.py new file mode 100644 index 0000000..b389ab4 --- /dev/null +++ b/src/backend/core/migrations/0001_initial.py @@ -0,0 +1,196 @@ +# Generated by Django 5.2.3 on 2025-06-26 12:15 + +import uuid + +import django.core.validators +from django.contrib.postgres.operations import CreateExtension, TrigramExtension, UnaccentExtension +from django.db import migrations, models + +import timezone_field.fields + +import core.models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + ("auth", "0012_alter_user_first_name_max_length"), + ] + + operations = [ + TrigramExtension(), + UnaccentExtension(), + CreateExtension("fuzzystrmatch"), # to use levenshtein + migrations.CreateModel( + name="User", + fields=[ + ("password", models.CharField(max_length=128, verbose_name="password")), + ( + "last_login", + models.DateTimeField(blank=True, null=True, verbose_name="last login"), + ), + ( + "is_superuser", + models.BooleanField( + default=False, + help_text="Designates that this user has all permissions without explicitly assigning them.", + verbose_name="superuser status", + ), + ), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + help_text="primary key for the record as UUID", + primary_key=True, + serialize=False, + verbose_name="id", + ), + ), + ( + "created_at", + models.DateTimeField( + auto_now_add=True, + help_text="date and time at which a record was created", + verbose_name="created on", + ), + ), + ( + "updated_at", + models.DateTimeField( + auto_now=True, + help_text="date and time at which a record was last updated", + verbose_name="updated on", + ), + ), + ( + "sub", + models.CharField( + blank=True, + help_text="Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only.", + max_length=255, + null=True, + unique=True, + validators=[ + django.core.validators.RegexValidator( + message="Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters.", + regex="^[\\w.@+-:]+\\Z", + ) + ], + verbose_name="sub", + ), + ), + ( + "full_name", + models.CharField( + blank=True, max_length=100, null=True, verbose_name="full name" + ), + ), + ( + "short_name", + models.CharField( + blank=True, max_length=20, null=True, verbose_name="short name" + ), + ), + ( + "email", + models.EmailField( + blank=True, max_length=254, null=True, verbose_name="identity email address" + ), + ), + ( + "admin_email", + models.EmailField( + blank=True, + max_length=254, + null=True, + unique=True, + verbose_name="admin email address", + ), + ), + ( + "language", + models.CharField( + blank=True, + choices=[ + ("en-us", "English"), + ("fr-fr", "Franรงais"), + ("de-de", "Deutsch"), + ("nl-nl", "Nederlands"), + ("es-es", "Espaรฑol"), + ], + default=None, + help_text="The language in which the user wants to see the interface.", + max_length=10, + null=True, + verbose_name="language", + ), + ), + ( + "timezone", + timezone_field.fields.TimeZoneField( + choices_display="WITH_GMT_OFFSET", + default="UTC", + help_text="The timezone in which the user wants to see times.", + use_pytz=False, + ), + ), + ( + "is_device", + models.BooleanField( + default=False, + help_text="Whether the user is a device or a real user.", + verbose_name="device", + ), + ), + ( + "is_staff", + models.BooleanField( + default=False, + help_text="Whether the user can log into this admin site.", + verbose_name="staff status", + ), + ), + ( + "is_active", + models.BooleanField( + default=True, + help_text="Whether this user should be treated as active. Unselect this instead of deleting accounts.", + verbose_name="active", + ), + ), + ( + "groups", + models.ManyToManyField( + blank=True, + help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", + related_name="user_set", + related_query_name="user", + to="auth.group", + verbose_name="groups", + ), + ), + ( + "user_permissions", + models.ManyToManyField( + blank=True, + help_text="Specific permissions for this user.", + related_name="user_set", + related_query_name="user", + to="auth.permission", + verbose_name="user permissions", + ), + ), + ], + options={ + "verbose_name": "user", + "verbose_name_plural": "users", + "db_table": "conversations_user", + }, + managers=[ + ("objects", core.models.UserManager()), + ], + ), + ] diff --git a/src/backend/core/migrations/__init__.py b/src/backend/core/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/backend/core/models.py b/src/backend/core/models.py new file mode 100644 index 0000000..4945ab5 --- /dev/null +++ b/src/backend/core/models.py @@ -0,0 +1,177 @@ +""" +Declare and configure the models for the conversations core application +""" + +import uuid +from logging import getLogger + +from django.conf import settings +from django.contrib.auth import models as auth_models +from django.contrib.auth.base_user import AbstractBaseUser +from django.core import mail, validators +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from timezone_field import TimeZoneField + +logger = getLogger(__name__) + + +class DuplicateEmailError(Exception): + """Raised when an email is already associated with a pre-existing user.""" + + def __init__(self, message=None, email=None): + """Set message and email to describe the exception.""" + self.message = message + self.email = email + super().__init__(self.message) + + +class BaseModel(models.Model): + """ + Serves as an abstract base model for other models, ensuring that records are validated + before saving as Django doesn't do it by default. + + Includes fields common to all models: a UUID primary key and creation/update timestamps. + """ + + id = models.UUIDField( + verbose_name=_("id"), + help_text=_("primary key for the record as UUID"), + primary_key=True, + default=uuid.uuid4, + editable=False, + ) + created_at = models.DateTimeField( + verbose_name=_("created on"), + help_text=_("date and time at which a record was created"), + auto_now_add=True, + editable=False, + ) + updated_at = models.DateTimeField( + verbose_name=_("updated on"), + help_text=_("date and time at which a record was last updated"), + auto_now=True, + editable=False, + ) + + class Meta: # pylint:disable=missing-class-docstring + abstract = True + + def save(self, *args, **kwargs): + """Call `full_clean` before saving.""" + self.full_clean() + super().save(*args, **kwargs) + + +class UserManager(auth_models.UserManager): + """Custom manager for User model with additional methods.""" + + def get_user_by_sub_or_email(self, sub, email): + """Fetch existing user by sub or email.""" + try: + return self.get(sub=sub) + except self.model.DoesNotExist as err: + if not email: + return None + + if settings.OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION: + try: + return self.get(email=email) + except self.model.DoesNotExist: + pass + elif self.filter(email=email).exists() and not settings.OIDC_ALLOW_DUPLICATE_EMAILS: + raise DuplicateEmailError( + _( + "We couldn't find a user with this sub but the email is already " + "associated with a registered user." + ) + ) from err + return None + + +class User(AbstractBaseUser, BaseModel, auth_models.PermissionsMixin): + """User model to work with OIDC only authentication.""" + + sub_validator = validators.RegexValidator( + regex=r"^[\w.@+-:]+\Z", + message=_( + "Enter a valid sub. This value may contain only letters, " + "numbers, and @/./+/-/_/: characters." + ), + ) + + sub = models.CharField( + _("sub"), + help_text=_( + "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only." + ), + max_length=255, + unique=True, + validators=[sub_validator], + blank=True, + null=True, + ) + + full_name = models.CharField(_("full name"), max_length=100, null=True, blank=True) + short_name = models.CharField(_("short name"), max_length=20, null=True, blank=True) + + email = models.EmailField(_("identity email address"), blank=True, null=True) + + # Unlike the "email" field which stores the email coming from the OIDC token, this field + # stores the email used by staff users to login to the admin site + admin_email = models.EmailField(_("admin email address"), unique=True, blank=True, null=True) + + language = models.CharField( + max_length=10, + choices=settings.LANGUAGES, + default=None, + verbose_name=_("language"), + help_text=_("The language in which the user wants to see the interface."), + null=True, + blank=True, + ) + timezone = TimeZoneField( + choices_display="WITH_GMT_OFFSET", + use_pytz=False, + default=settings.TIME_ZONE, + help_text=_("The timezone in which the user wants to see times."), + ) + is_device = models.BooleanField( + _("device"), + default=False, + help_text=_("Whether the user is a device or a real user."), + ) + is_staff = models.BooleanField( + _("staff status"), + default=False, + help_text=_("Whether the user can log into this admin site."), + ) + is_active = models.BooleanField( + _("active"), + default=True, + help_text=_( + "Whether this user should be treated as active. " + "Unselect this instead of deleting accounts." + ), + ) + + objects = UserManager() + + USERNAME_FIELD = "admin_email" + REQUIRED_FIELDS = [] + + class Meta: # pylint:disable=missing-class-docstring + db_table = "conversations_user" + verbose_name = _("user") + verbose_name_plural = _("users") + + def __str__(self): + """Return a string representation of the user.""" + return self.email or self.admin_email or str(self.id) + + def email_user(self, subject, message, from_email=None, **kwargs): + """Email this user.""" + if not self.email: + raise ValueError("User has no email address.") + mail.send_mail(subject, message, from_email, [self.email], **kwargs) diff --git a/src/backend/core/permissions.py b/src/backend/core/permissions.py new file mode 100644 index 0000000..6260991 --- /dev/null +++ b/src/backend/core/permissions.py @@ -0,0 +1,33 @@ +"""Permission handlers for the conversations core app.""" + +from rest_framework import permissions + + +class IsAuthenticated(permissions.BasePermission): + """ + Allows access only to authenticated users. Alternative method checking the presence + of the auth token to avoid hitting the database. + """ + + def has_permission(self, request, view): + return bool(request.auth) or request.user.is_authenticated + + +class IsAuthenticatedOrSafe(IsAuthenticated): + """Allows access to authenticated users (or anonymous users but only on safe methods).""" + + def has_permission(self, request, view): + if request.method in permissions.SAFE_METHODS: + return True + return super().has_permission(request, view) + + +class IsSelf(IsAuthenticated): + """ + Allows access only to authenticated users. Alternative method checking the presence + of the auth token to avoid hitting the database. + """ + + def has_object_permission(self, request, view, obj): + """Write permissions are only allowed to the user itself.""" + return obj == request.user diff --git a/src/backend/core/static/images/logo.png b/src/backend/core/static/images/logo.png new file mode 100644 index 0000000..bdd8de5 Binary files /dev/null and b/src/backend/core/static/images/logo.png differ diff --git a/src/backend/core/templates/core/generate_document.html b/src/backend/core/templates/core/generate_document.html new file mode 100644 index 0000000..ac9f291 --- /dev/null +++ b/src/backend/core/templates/core/generate_document.html @@ -0,0 +1,14 @@ + + + + Generate Document + + +

Generate Document

+
+ {% csrf_token %} + {{ form.as_p }} + +
+ + diff --git a/src/backend/core/templatetags/__init__.py b/src/backend/core/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/backend/core/templatetags/extra_tags.py b/src/backend/core/templatetags/extra_tags.py new file mode 100644 index 0000000..109bd7b --- /dev/null +++ b/src/backend/core/templatetags/extra_tags.py @@ -0,0 +1,58 @@ +"""Custom template tags for the core application of People.""" + +import base64 + +from django import template +from django.contrib.staticfiles import finders + +from PIL import ImageFile as PillowImageFile + +register = template.Library() + + +def image_to_base64(file_or_path, close=False): + """ + Return the src string of the base64 encoding of an image represented by its path + or file opened or not. + + Inspired by Django's "get_image_dimensions" + """ + pil_parser = PillowImageFile.Parser() + if hasattr(file_or_path, "read"): + file = file_or_path + if file.closed and hasattr(file, "open"): + file_or_path.open() + file_pos = file.tell() + file.seek(0) + else: + try: + # pylint: disable=consider-using-with + file = open(file_or_path, "rb") + except OSError: + return "" + close = True + + try: + image_data = file.read() + if not image_data: + return "" + pil_parser.feed(image_data) + if pil_parser.image: + mime_type = pil_parser.image.get_format_mimetype() + encoded_string = base64.b64encode(image_data) + return f"data:{mime_type:s};base64, {encoded_string.decode('utf-8'):s}" + return "" + finally: + if close: + file.close() + else: + file.seek(file_pos) + + +@register.simple_tag +def base64_static(path): + """Return a static file into a base64.""" + full_path = finders.find(path) + if full_path: + return image_to_base64(full_path, True) + return "" diff --git a/src/backend/core/tests/__init__.py b/src/backend/core/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/backend/core/tests/authentication/__init__.py b/src/backend/core/tests/authentication/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/backend/core/tests/authentication/test_backends.py b/src/backend/core/tests/authentication/test_backends.py new file mode 100644 index 0000000..11769bb --- /dev/null +++ b/src/backend/core/tests/authentication/test_backends.py @@ -0,0 +1,487 @@ +"""Unit tests for the Authentication Backends.""" + +import random +import re + +from django.core.exceptions import SuspiciousOperation +from django.test.utils import override_settings + +import pytest +import responses +from cryptography.fernet import Fernet +from lasuite.oidc_login.backends import get_oidc_refresh_token + +from core import models +from core.authentication.backends import OIDCAuthenticationBackend +from core.factories import UserFactory + +pytestmark = pytest.mark.django_db + + +def test_authentication_getter_existing_user_no_email(django_assert_num_queries, monkeypatch): + """ + If an existing user matches the user's info sub, the user should be returned. + """ + + klass = OIDCAuthenticationBackend() + db_user = UserFactory() + + def get_userinfo_mocked(*args): + return {"sub": db_user.sub} + + monkeypatch.setattr(OIDCAuthenticationBackend, "get_userinfo", get_userinfo_mocked) + + with django_assert_num_queries(1): + user = klass.get_or_create_user(access_token="test-token", id_token=None, payload=None) + + assert user == db_user + + +def test_authentication_getter_existing_user_via_email(django_assert_num_queries, monkeypatch): + """ + If an existing user doesn't match the sub but matches the email, + the user should be returned. + """ + + klass = OIDCAuthenticationBackend() + db_user = UserFactory() + + def get_userinfo_mocked(*args): + return {"sub": "123", "email": db_user.email} + + monkeypatch.setattr(OIDCAuthenticationBackend, "get_userinfo", get_userinfo_mocked) + + with django_assert_num_queries(3): # user by sub, user by mail, update sub + user = klass.get_or_create_user(access_token="test-token", id_token=None, payload=None) + + assert user == db_user + + +def test_authentication_getter_email_none(monkeypatch): + """ + If no user is found with the sub and no email is provided, a new user should be created. + """ + + klass = OIDCAuthenticationBackend() + db_user = UserFactory(email=None) + + def get_userinfo_mocked(*args): + user_info = {"sub": "123"} + if random.choice([True, False]): + user_info["email"] = None + return user_info + + monkeypatch.setattr(OIDCAuthenticationBackend, "get_userinfo", get_userinfo_mocked) + + user = klass.get_or_create_user(access_token="test-token", id_token=None, payload=None) + + # Since the sub and email didn't match, it should create a new user + assert models.User.objects.count() == 2 + assert user != db_user + assert user.sub == "123" + + +def test_authentication_getter_existing_user_no_fallback_to_email_allow_duplicate( + settings, monkeypatch +): + """ + When the "OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION" setting is set to False, + the system should not match users by email, even if the email matches. + """ + + klass = OIDCAuthenticationBackend() + db_user = UserFactory() + + # Set the setting to False + settings.OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION = False + settings.OIDC_ALLOW_DUPLICATE_EMAILS = True + + def get_userinfo_mocked(*args): + return {"sub": "123", "email": db_user.email} + + monkeypatch.setattr(OIDCAuthenticationBackend, "get_userinfo", get_userinfo_mocked) + + user = klass.get_or_create_user(access_token="test-token", id_token=None, payload=None) + + # Since the sub doesn't match, it should create a new user + assert models.User.objects.count() == 2 + assert user != db_user + assert user.sub == "123" + + +def test_authentication_getter_existing_user_no_fallback_to_email_no_duplicate( + settings, monkeypatch +): + """ + When the "OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION" setting is set to False, + the system should not match users by email, even if the email matches. + """ + + klass = OIDCAuthenticationBackend() + db_user = UserFactory() + + # Set the setting to False + settings.OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION = False + settings.OIDC_ALLOW_DUPLICATE_EMAILS = False + + def get_userinfo_mocked(*args): + return {"sub": "123", "email": db_user.email} + + monkeypatch.setattr(OIDCAuthenticationBackend, "get_userinfo", get_userinfo_mocked) + + with pytest.raises( + SuspiciousOperation, + match=( + "We couldn't find a user with this sub but the email is " + "already associated with a registered user." + ), + ): + klass.get_or_create_user(access_token="test-token", id_token=None, payload=None) + + # Since the sub doesn't match, it should not create a new user + assert models.User.objects.count() == 1 + + +def test_authentication_getter_existing_user_with_email(django_assert_num_queries, monkeypatch): + """ + When the user's info contains an email and targets an existing user, + """ + klass = OIDCAuthenticationBackend() + user = UserFactory(full_name="John Doe", short_name="John") + + def get_userinfo_mocked(*args): + return { + "sub": user.sub, + "email": user.email, + "first_name": "John", + "last_name": "Doe", + } + + monkeypatch.setattr(OIDCAuthenticationBackend, "get_userinfo", get_userinfo_mocked) + + # Only 1 query because email and names have not changed + with django_assert_num_queries(1): + authenticated_user = klass.get_or_create_user( + access_token="test-token", id_token=None, payload=None + ) + + assert user == authenticated_user + + +@pytest.mark.parametrize( + "first_name, last_name, email", + [ + ("Jack", "Doe", "john.doe@example.com"), + ("John", "Duy", "john.doe@example.com"), + ("John", "Doe", "jack.duy@example.com"), + ("Jack", "Duy", "jack.duy@example.com"), + ], +) +def test_authentication_getter_existing_user_change_fields_sub( + first_name, last_name, email, django_assert_num_queries, monkeypatch +): + """ + It should update the email or name fields on the user when they change + and the user was identified by its "sub". + """ + klass = OIDCAuthenticationBackend() + user = UserFactory(full_name="John Doe", short_name="John", email="john.doe@example.com") + + def get_userinfo_mocked(*args): + return { + "sub": user.sub, + "email": email, + "first_name": first_name, + "last_name": last_name, + } + + monkeypatch.setattr(OIDCAuthenticationBackend, "get_userinfo", get_userinfo_mocked) + + # One and only one additional update query when a field has changed + with django_assert_num_queries(2): + authenticated_user = klass.get_or_create_user( + access_token="test-token", id_token=None, payload=None + ) + + assert user == authenticated_user + user.refresh_from_db() + assert user.email == email + assert user.full_name == f"{first_name:s} {last_name:s}" + assert user.short_name == first_name + + +@pytest.mark.parametrize( + "first_name, last_name, email", + [ + ("Jack", "Doe", "john.doe@example.com"), + ("John", "Duy", "john.doe@example.com"), + ], +) +def test_authentication_getter_existing_user_change_fields_email( + first_name, last_name, email, django_assert_num_queries, monkeypatch +): + """ + It should update the name fields on the user when they change + and the user was identified by its "email" as fallback. + """ + klass = OIDCAuthenticationBackend() + user = UserFactory(full_name="John Doe", short_name="John", email="john.doe@example.com") + + def get_userinfo_mocked(*args): + return { + "sub": "123", + "email": user.email, + "first_name": first_name, + "last_name": last_name, + } + + monkeypatch.setattr(OIDCAuthenticationBackend, "get_userinfo", get_userinfo_mocked) + + # One and only one additional update query when a field has changed + with django_assert_num_queries(3): + authenticated_user = klass.get_or_create_user( + access_token="test-token", id_token=None, payload=None + ) + + assert user == authenticated_user + user.refresh_from_db() + assert user.email == email + assert user.full_name == f"{first_name:s} {last_name:s}" + assert user.short_name == first_name + + +def test_authentication_getter_new_user_no_email(monkeypatch): + """ + If no user matches the user's info sub, a user should be created. + User's info doesn't contain an email, created user's email should be empty. + """ + klass = OIDCAuthenticationBackend() + + def get_userinfo_mocked(*args): + return {"sub": "123"} + + monkeypatch.setattr(OIDCAuthenticationBackend, "get_userinfo", get_userinfo_mocked) + + user = klass.get_or_create_user(access_token="test-token", id_token=None, payload=None) + + assert user.sub == "123" + assert user.email is None + assert user.full_name is None + assert user.short_name is None + assert user.has_usable_password() is False + assert models.User.objects.count() == 1 + + +def test_authentication_getter_new_user_with_email(monkeypatch): + """ + If no user matches the user's info sub, a user should be created. + User's email and name should be set on the identity. + The "email" field on the User model should not be set as it is reserved for staff users. + """ + klass = OIDCAuthenticationBackend() + + email = "conversations@example.com" + + def get_userinfo_mocked(*args): + return {"sub": "123", "email": email, "first_name": "John", "last_name": "Doe"} + + monkeypatch.setattr(OIDCAuthenticationBackend, "get_userinfo", get_userinfo_mocked) + + user = klass.get_or_create_user(access_token="test-token", id_token=None, payload=None) + + assert user.sub == "123" + assert user.email == email + assert user.full_name == "John Doe" + assert user.short_name == "John" + assert user.has_usable_password() is False + assert models.User.objects.count() == 1 + + +@override_settings(OIDC_OP_USER_ENDPOINT="http://oidc.endpoint.test/userinfo") +@responses.activate +def test_authentication_get_userinfo_json_response(): + """Test get_userinfo method with a JSON response.""" + + responses.add( + responses.GET, + re.compile(r".*/userinfo"), + json={ + "first_name": "John", + "last_name": "Doe", + "email": "john.doe@example.com", + }, + status=200, + ) + + oidc_backend = OIDCAuthenticationBackend() + result = oidc_backend.get_userinfo("fake_access_token", None, None) + + assert result["first_name"] == "John" + assert result["last_name"] == "Doe" + assert result["email"] == "john.doe@example.com" + + +@override_settings(OIDC_OP_USER_ENDPOINT="http://oidc.endpoint.test/userinfo") +@responses.activate +def test_authentication_get_userinfo_token_response(monkeypatch, settings): + """Test get_userinfo method with a token response.""" + settings.OIDC_RP_SIGN_ALGO = "HS256" # disable JWKS URL call + responses.add( + responses.GET, + re.compile(r".*/userinfo"), + body="fake.jwt.token", + status=200, + content_type="application/jwt", + ) + + def mock_verify_token(self, token): # pylint: disable=unused-argument + return { + "first_name": "Jane", + "last_name": "Doe", + "email": "jane.doe@example.com", + } + + monkeypatch.setattr(OIDCAuthenticationBackend, "verify_token", mock_verify_token) + + oidc_backend = OIDCAuthenticationBackend() + result = oidc_backend.get_userinfo("fake_access_token", None, None) + + assert result["first_name"] == "Jane" + assert result["last_name"] == "Doe" + assert result["email"] == "jane.doe@example.com" + + +@override_settings(OIDC_OP_USER_ENDPOINT="http://oidc.endpoint.test/userinfo") +@responses.activate +def test_authentication_get_userinfo_invalid_response(settings): + """ + Test get_userinfo method with an invalid JWT response that + causes verify_token to raise an error. + """ + settings.OIDC_RP_SIGN_ALGO = "HS256" # disable JWKS URL call + responses.add( + responses.GET, + re.compile(r".*/userinfo"), + body="fake.jwt.token", + status=200, + content_type="application/jwt", + ) + + oidc_backend = OIDCAuthenticationBackend() + + with pytest.raises( + SuspiciousOperation, + match="User info response was not valid JWT", + ): + oidc_backend.get_userinfo("fake_access_token", None, None) + + +def test_authentication_getter_existing_disabled_user_via_sub( + django_assert_num_queries, monkeypatch +): + """ + If an existing user matches the sub but is disabled, + an error should be raised and a user should not be created. + """ + + klass = OIDCAuthenticationBackend() + db_user = UserFactory(is_active=False) + + def get_userinfo_mocked(*args): + return { + "sub": db_user.sub, + "email": db_user.email, + "first_name": "John", + "last_name": "Doe", + } + + monkeypatch.setattr(OIDCAuthenticationBackend, "get_userinfo", get_userinfo_mocked) + + with ( + django_assert_num_queries(1), + pytest.raises(SuspiciousOperation, match="User account is disabled"), + ): + klass.get_or_create_user(access_token="test-token", id_token=None, payload=None) + + assert models.User.objects.count() == 1 + + +def test_authentication_getter_existing_disabled_user_via_email( + django_assert_num_queries, monkeypatch +): + """ + If an existing user does not match the sub but matches the email and is disabled, + an error should be raised and a user should not be created. + """ + + klass = OIDCAuthenticationBackend() + db_user = UserFactory(is_active=False) + + def get_userinfo_mocked(*args): + return { + "sub": "random", + "email": db_user.email, + "first_name": "John", + "last_name": "Doe", + } + + monkeypatch.setattr(OIDCAuthenticationBackend, "get_userinfo", get_userinfo_mocked) + + with ( + django_assert_num_queries(2), + pytest.raises(SuspiciousOperation, match="User account is disabled"), + ): + klass.get_or_create_user(access_token="test-token", id_token=None, payload=None) + + assert models.User.objects.count() == 1 + + +@responses.activate +def test_authentication_session_tokens(django_assert_num_queries, monkeypatch, rf, settings): + """ + Test that the session contains oidc_refresh_token and oidc_access_token after authentication. + """ + settings.OIDC_OP_TOKEN_ENDPOINT = "http://oidc.endpoint.test/token" + settings.OIDC_OP_USER_ENDPOINT = "http://oidc.endpoint.test/userinfo" + settings.OIDC_OP_JWKS_ENDPOINT = "http://oidc.endpoint.test/jwks" + settings.OIDC_STORE_ACCESS_TOKEN = True + settings.OIDC_STORE_REFRESH_TOKEN = True + settings.OIDC_STORE_REFRESH_TOKEN_KEY = Fernet.generate_key() + + klass = OIDCAuthenticationBackend() + request = rf.get("/some-url", {"state": "test-state", "code": "test-code"}) + request.session = {} + + def verify_token_mocked(*args, **kwargs): + return {"sub": "123", "email": "test@example.com"} + + monkeypatch.setattr(OIDCAuthenticationBackend, "verify_token", verify_token_mocked) + + responses.add( + responses.POST, + re.compile(settings.OIDC_OP_TOKEN_ENDPOINT), + json={ + "access_token": "test-access-token", + "refresh_token": "test-refresh-token", + }, + status=200, + ) + + responses.add( + responses.GET, + re.compile(settings.OIDC_OP_USER_ENDPOINT), + json={"sub": "123", "email": "test@example.com"}, + status=200, + ) + + with django_assert_num_queries(5): + user = klass.authenticate( + request, + code="test-code", + nonce="test-nonce", + code_verifier="test-code-verifier", + ) + + assert user is not None + assert request.session["oidc_access_token"] == "test-access-token" + assert get_oidc_refresh_token(request.session) == "test-refresh-token" diff --git a/src/backend/core/tests/conftest.py b/src/backend/core/tests/conftest.py new file mode 100644 index 0000000..ed4f86d --- /dev/null +++ b/src/backend/core/tests/conftest.py @@ -0,0 +1,24 @@ +"""Fixtures for tests in the conversations core application""" + +from unittest import mock + +from django.core.cache import cache + +import pytest + +USER = "user" +TEAM = "team" +VIA = [USER, TEAM] + + +@pytest.fixture(autouse=True) +def clear_cache(): + """Fixture to clear the cache before each test.""" + cache.clear() + + +@pytest.fixture +def mock_user_teams(): + """Mock for the "teams" property on the User model.""" + with mock.patch("core.models.User.teams", new_callable=mock.PropertyMock) as mock_teams: + yield mock_teams diff --git a/src/backend/core/tests/swagger/__init__.py b/src/backend/core/tests/swagger/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/backend/core/tests/swagger/test_openapi_schema.py b/src/backend/core/tests/swagger/test_openapi_schema.py new file mode 100644 index 0000000..a5e299f --- /dev/null +++ b/src/backend/core/tests/swagger/test_openapi_schema.py @@ -0,0 +1,40 @@ +""" +Test suite for generated openapi schema. +""" + +import json +from io import StringIO + +from django.core.management import call_command +from django.test import Client + +import pytest + +pytestmark = pytest.mark.django_db + + +def test_openapi_client_schema(): + """ + Generated and served OpenAPI client schema should be correct. + """ + # Start by generating the swagger.json file + output = StringIO() + call_command( + "spectacular", + "--api-version", + "v1.0", + "--urlconf", + "core.urls", + "--format", + "openapi-json", + "--file", + "core/tests/swagger/swagger.json", + stdout=output, + ) + assert output.getvalue() == "" + + response = Client().get("/api/v1.0/swagger.json") + + assert response.status_code == 200 + with open("core/tests/swagger/swagger.json", "r", encoding="utf-8") as expected_schema: + assert response.json() == json.load(expected_schema) diff --git a/src/backend/core/tests/test_api_config.py b/src/backend/core/tests/test_api_config.py new file mode 100644 index 0000000..8c93f27 --- /dev/null +++ b/src/backend/core/tests/test_api_config.py @@ -0,0 +1,151 @@ +""" +Test config API endpoints in the Conversations core app. +""" + +import json + +from django.test import override_settings + +import pytest +from rest_framework.status import ( + HTTP_200_OK, +) +from rest_framework.test import APIClient + +from core import factories + +pytestmark = pytest.mark.django_db + + +@override_settings( + CRISP_WEBSITE_ID="123", + FRONTEND_CSS_URL="http://testcss/", + FRONTEND_THEME="test-theme", + MEDIA_BASE_URL="http://testserver/", + POSTHOG_KEY={"id": "132456", "host": "https://eu.i.posthog-test.com"}, + SENTRY_DSN="https://sentry.test/123", + THEME_CUSTOMIZATION_FILE_PATH="", +) +@pytest.mark.parametrize("is_authenticated", [False, True]) +def test_api_config(is_authenticated): + """Anonymous users should be allowed to get the configuration.""" + client = APIClient() + + if is_authenticated: + user = factories.UserFactory() + client.force_login(user) + + response = client.get("/api/v1.0/config/") + assert response.status_code == HTTP_200_OK + assert response.json() == { + "CRISP_WEBSITE_ID": "123", + "ENVIRONMENT": "test", + "FRONTEND_CSS_URL": "http://testcss/", + "FRONTEND_HOMEPAGE_FEATURE_ENABLED": True, + "FRONTEND_THEME": "test-theme", + "LANGUAGES": [ + ["en-us", "English"], + ["fr-fr", "Franรงais"], + ["de-de", "Deutsch"], + ["nl-nl", "Nederlands"], + ["es-es", "Espaรฑol"], + ], + "LANGUAGE_CODE": "en-us", + "MEDIA_BASE_URL": "http://testserver/", + "POSTHOG_KEY": {"id": "132456", "host": "https://eu.i.posthog-test.com"}, + "SENTRY_DSN": "https://sentry.test/123", + "theme_customization": {}, + } + + +@override_settings( + THEME_CUSTOMIZATION_FILE_PATH="/not/existing/file.json", +) +@pytest.mark.parametrize("is_authenticated", [False, True]) +def test_api_config_with_invalid_theme_customization_file(is_authenticated): + """Anonymous users should be allowed to get the configuration.""" + client = APIClient() + + if is_authenticated: + user = factories.UserFactory() + client.force_login(user) + + response = client.get("/api/v1.0/config/") + assert response.status_code == HTTP_200_OK + content = response.json() + assert content["theme_customization"] == {} + + +@override_settings( + THEME_CUSTOMIZATION_FILE_PATH="/configuration/theme/invalid.json", +) +@pytest.mark.parametrize("is_authenticated", [False, True]) +def test_api_config_with_invalid_json_theme_customization_file(is_authenticated, fs): + """Anonymous users should be allowed to get the configuration.""" + fs.create_file( + "/configuration/theme/invalid.json", + contents="invalid json", + ) + client = APIClient() + + if is_authenticated: + user = factories.UserFactory() + client.force_login(user) + + response = client.get("/api/v1.0/config/") + assert response.status_code == HTTP_200_OK + content = response.json() + assert content["theme_customization"] == {} + + +@override_settings( + THEME_CUSTOMIZATION_FILE_PATH="/configuration/theme/default.json", +) +@pytest.mark.parametrize("is_authenticated", [False, True]) +def test_api_config_with_theme_customization(is_authenticated, fs): + """Anonymous users should be allowed to get the configuration.""" + fs.create_file( + "/configuration/theme/default.json", + contents=json.dumps( + { + "colors": { + "primary": "#000000", + "secondary": "#000000", + }, + } + ), + ) + client = APIClient() + + if is_authenticated: + user = factories.UserFactory() + client.force_login(user) + + response = client.get("/api/v1.0/config/") + assert response.status_code == HTTP_200_OK + content = response.json() + assert content["theme_customization"] == { + "colors": { + "primary": "#000000", + "secondary": "#000000", + }, + } + + +@pytest.mark.parametrize("is_authenticated", [False, True]) +def test_api_config_with_original_theme_customization(is_authenticated, settings): + """Anonymous users should be allowed to get the configuration.""" + client = APIClient() + + if is_authenticated: + user = factories.UserFactory() + client.force_login(user) + + response = client.get("/api/v1.0/config/") + assert response.status_code == HTTP_200_OK + content = response.json() + + with open(settings.THEME_CUSTOMIZATION_FILE_PATH, "r", encoding="utf-8") as f: + theme_customization = json.load(f) + + assert content["theme_customization"] == theme_customization diff --git a/src/backend/core/tests/test_api_users.py b/src/backend/core/tests/test_api_users.py new file mode 100644 index 0000000..2e55dba --- /dev/null +++ b/src/backend/core/tests/test_api_users.py @@ -0,0 +1,528 @@ +""" +Test users API endpoints in the conversations core app. +""" + +import pytest +from rest_framework.test import APIClient + +from core import factories, models +from core.api import serializers + +pytestmark = pytest.mark.django_db + + +def test_api_users_list_anonymous(): + """Anonymous users should not be allowed to list users.""" + factories.UserFactory() + client = APIClient() + response = client.get("/api/v1.0/users/") + assert response.status_code == 401 + assert response.json() == {"detail": "Authentication credentials were not provided."} + + +def test_api_users_list_authenticated(): + """ + Authenticated users should not be able to list users without a query. + """ + user = factories.UserFactory() + + client = APIClient() + client.force_login(user) + + factories.UserFactory.create_batch(2) + response = client.get( + "/api/v1.0/users/", + ) + assert response.status_code == 200 + content = response.json() + assert content == [] + + +def test_api_users_list_query_email(): + """ + Authenticated users should be able to list users and filter by email. + Only results with a Levenstein distance less than 3 with the query should be returned. + We want to match by Levenstein distance because we want to prevent typing errors. + """ + user = factories.UserFactory() + + client = APIClient() + client.force_login(user) + + dave = factories.UserFactory(email="david.bowman@work.com") + factories.UserFactory(email="nicole.bowman@work.com") + + response = client.get( + "/api/v1.0/users/?q=david.bowman@work.com", + ) + assert response.status_code == 200 + user_ids = [user["id"] for user in response.json()] + assert user_ids == [str(dave.id)] + + response = client.get( + "/api/v1.0/users/?q=davig.bovman@worm.com", + ) + assert response.status_code == 200 + user_ids = [user["id"] for user in response.json()] + assert user_ids == [str(dave.id)] + + response = client.get( + "/api/v1.0/users/?q=davig.bovman@worm.cop", + ) + assert response.status_code == 200 + user_ids = [user["id"] for user in response.json()] + assert user_ids == [] + + +def test_api_users_list_limit(settings): + """ + Authenticated users should be able to list users and the number of results + should be limited to 10. + """ + user = factories.UserFactory() + + client = APIClient() + client.force_login(user) + + # Use a base name with a length equal 5 to test that the limit is applied + base_name = "alice" + for i in range(15): + factories.UserFactory(email=f"{base_name}.{i}@example.com") + + response = client.get( + "/api/v1.0/users/?q=alice", + ) + assert response.status_code == 200 + assert len(response.json()) == 5 + + # if the limit is changed, all users should be returned + settings.API_USERS_LIST_LIMIT = 100 + response = client.get( + "/api/v1.0/users/?q=alice", + ) + assert response.status_code == 200 + assert len(response.json()) == 15 + + +def test_api_users_list_throttling_authenticated(settings): + """ + Authenticated users should be throttled. + """ + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["user_list_burst"] = "3/minute" + + for _i in range(3): + response = client.get( + "/api/v1.0/users/?q=alice", + ) + assert response.status_code == 200 + + response = client.get( + "/api/v1.0/users/?q=alice", + ) + assert response.status_code == 429 + + +def test_api_users_list_query_email_matching(): + """While filtering by email, results should be filtered and sorted by Levenstein distance.""" + user = factories.UserFactory() + + client = APIClient() + client.force_login(user) + + user1 = factories.UserFactory(email="alice.johnson@example.gouv.fr") + user2 = factories.UserFactory(email="alice.johnnson@example.gouv.fr") + user3 = factories.UserFactory(email="alice.kohlson@example.gouv.fr") + user4 = factories.UserFactory(email="alicia.johnnson@example.gouv.fr") + user5 = factories.UserFactory(email="alicia.johnnson@example.gov.uk") + factories.UserFactory(email="alice.thomson@example.gouv.fr") + + response = client.get( + "/api/v1.0/users/?q=alice.johnson@example.gouv.fr", + ) + assert response.status_code == 200 + user_ids = [user["id"] for user in response.json()] + assert user_ids == [str(user1.id), str(user2.id), str(user3.id), str(user4.id)] + + response = client.get("/api/v1.0/users/?q=alicia.johnnson@example.gouv.fr") + + assert response.status_code == 200 + user_ids = [user["id"] for user in response.json()] + assert user_ids == [str(user4.id), str(user2.id), str(user1.id), str(user5.id)] + + +def test_api_users_list_query_short_queries(): + """ + Queries shorter than 5 characters should return an empty result set. + """ + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + factories.UserFactory(email="john.doe@example.com") + factories.UserFactory(email="john.lennon@example.com") + + response = client.get("/api/v1.0/users/?q=jo") + assert response.status_code == 200 + assert response.json() == [] + + response = client.get("/api/v1.0/users/?q=john") + assert response.status_code == 200 + assert response.json() == [] + + response = client.get("/api/v1.0/users/?q=john.") + assert response.status_code == 200 + assert len(response.json()) == 2 + + +def test_api_users_list_query_inactive(): + """Inactive users should not be listed.""" + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + factories.UserFactory(email="john.doe@example.com", is_active=False) + lennon = factories.UserFactory(email="john.lennon@example.com") + + response = client.get("/api/v1.0/users/?q=john.") + + assert response.status_code == 200 + user_ids = [user["id"] for user in response.json()] + assert user_ids == [str(lennon.id)] + + +def test_api_users_retrieve_me_anonymous(): + """Anonymous users should not be allowed to list users.""" + factories.UserFactory.create_batch(2) + client = APIClient() + response = client.get("/api/v1.0/users/me/") + assert response.status_code == 401 + assert response.json() == {"detail": "Authentication credentials were not provided."} + + +def test_api_users_retrieve_me_authenticated(): + """Authenticated users should be able to retrieve their own user via the "/users/me" path.""" + user = factories.UserFactory() + + client = APIClient() + client.force_login(user) + + factories.UserFactory.create_batch(2) + response = client.get( + "/api/v1.0/users/me/", + ) + + assert response.status_code == 200 + assert response.json() == { + "id": str(user.id), + "email": user.email, + "full_name": user.full_name, + "language": user.language, + "short_name": user.short_name, + } + + +def test_api_users_retrieve_anonymous(): + """Anonymous users should not be allowed to retrieve a user.""" + client = APIClient() + user = factories.UserFactory() + response = client.get(f"/api/v1.0/users/{user.id!s}/") + + assert response.status_code == 401 + assert response.json() == {"detail": "Authentication credentials were not provided."} + + +def test_api_users_retrieve_authenticated_self(): + """ + Authenticated users should be allowed to retrieve their own user. + The returned object should not contain the password. + """ + user = factories.UserFactory() + + client = APIClient() + client.force_login(user) + + response = client.get( + f"/api/v1.0/users/{user.id!s}/", + ) + assert response.status_code == 405 + assert response.json() == {"detail": 'Method "GET" not allowed.'} + + +def test_api_users_retrieve_authenticated_other(): + """ + Authenticated users should be able to retrieve another user's detail view with + limited information. + """ + user = factories.UserFactory() + + client = APIClient() + client.force_login(user) + + other_user = factories.UserFactory() + + response = client.get( + f"/api/v1.0/users/{other_user.id!s}/", + ) + assert response.status_code == 405 + assert response.json() == {"detail": 'Method "GET" not allowed.'} + + +def test_api_users_create_anonymous(): + """Anonymous users should not be able to create users via the API.""" + response = APIClient().post( + "/api/v1.0/users/", + { + "language": "fr-fr", + "password": "mypassword", + }, + ) + assert response.status_code == 401 + assert response.json() == {"detail": "Authentication credentials were not provided."} + assert models.User.objects.exists() is False + + +def test_api_users_create_authenticated(): + """Authenticated users should not be able to create users via the API.""" + user = factories.UserFactory() + + client = APIClient() + client.force_login(user) + + response = client.post( + "/api/v1.0/users/", + { + "language": "fr-fr", + "password": "mypassword", + }, + format="json", + ) + assert response.status_code == 405 + assert response.json() == {"detail": 'Method "POST" not allowed.'} + assert models.User.objects.exclude(id=user.id).exists() is False + + +def test_api_users_update_anonymous(): + """Anonymous users should not be able to update users via the API.""" + user = factories.UserFactory() + + old_user_values = dict(serializers.UserSerializer(instance=user).data) + new_user_values = serializers.UserSerializer(instance=factories.UserFactory()).data + + response = APIClient().put( + f"/api/v1.0/users/{user.id!s}/", + new_user_values, + format="json", + ) + + assert response.status_code == 401 + assert response.json() == {"detail": "Authentication credentials were not provided."} + + user.refresh_from_db() + user_values = dict(serializers.UserSerializer(instance=user).data) + for key, value in user_values.items(): + assert value == old_user_values[key] + + +def test_api_users_update_authenticated_self(): + """ + Authenticated users should be able to update their own user but only "language" + and "timezone" fields. + """ + user = factories.UserFactory() + + client = APIClient() + client.force_login(user) + + old_user_values = dict(serializers.UserSerializer(instance=user).data) + new_user_values = dict(serializers.UserSerializer(instance=factories.UserFactory()).data) + + response = client.put( + f"/api/v1.0/users/{user.id!s}/", + new_user_values, + format="json", + ) + + assert response.status_code == 200 + user.refresh_from_db() + user_values = dict(serializers.UserSerializer(instance=user).data) + for key, value in user_values.items(): + if key in ["language", "timezone"]: + assert value == new_user_values[key] + else: + assert value == old_user_values[key] + + +def test_api_users_update_authenticated_other(): + """Authenticated users should not be allowed to update other users.""" + user = factories.UserFactory() + + client = APIClient() + client.force_login(user) + + user = factories.UserFactory() + old_user_values = dict(serializers.UserSerializer(instance=user).data) + new_user_values = serializers.UserSerializer(instance=factories.UserFactory()).data + + response = client.put( + f"/api/v1.0/users/{user.id!s}/", + new_user_values, + format="json", + ) + + assert response.status_code == 403 + user.refresh_from_db() + user_values = dict(serializers.UserSerializer(instance=user).data) + for key, value in user_values.items(): + assert value == old_user_values[key] + + +def test_api_users_patch_anonymous(): + """Anonymous users should not be able to patch users via the API.""" + user = factories.UserFactory() + + old_user_values = dict(serializers.UserSerializer(instance=user).data) + new_user_values = dict(serializers.UserSerializer(instance=factories.UserFactory()).data) + + for key, new_value in new_user_values.items(): + response = APIClient().patch( + f"/api/v1.0/users/{user.id!s}/", + {key: new_value}, + format="json", + ) + assert response.status_code == 401 + assert response.json() == {"detail": "Authentication credentials were not provided."} + + user.refresh_from_db() + user_values = dict(serializers.UserSerializer(instance=user).data) + for key, value in user_values.items(): + assert value == old_user_values[key] + + +def test_api_users_patch_authenticated_self(): + """ + Authenticated users should be able to patch their own user but only "language" + and "timezone" fields. + """ + user = factories.UserFactory() + + client = APIClient() + client.force_login(user) + + old_user_values = dict(serializers.UserSerializer(instance=user).data) + new_user_values = dict(serializers.UserSerializer(instance=factories.UserFactory()).data) + + for key, new_value in new_user_values.items(): + response = client.patch( + f"/api/v1.0/users/{user.id!s}/", + {key: new_value}, + format="json", + ) + assert response.status_code == 200 + + user.refresh_from_db() + user_values = dict(serializers.UserSerializer(instance=user).data) + for key, value in user_values.items(): + if key in ["language", "timezone"]: + assert value == new_user_values[key] + else: + assert value == old_user_values[key] + + +def test_api_users_patch_authenticated_other(): + """Authenticated users should not be allowed to patch other users.""" + user = factories.UserFactory() + + client = APIClient() + client.force_login(user) + + user = factories.UserFactory() + old_user_values = dict(serializers.UserSerializer(instance=user).data) + new_user_values = dict(serializers.UserSerializer(instance=factories.UserFactory()).data) + + for key, new_value in new_user_values.items(): + response = client.put( + f"/api/v1.0/users/{user.id!s}/", + {key: new_value}, + format="json", + ) + assert response.status_code == 403 + + user.refresh_from_db() + user_values = dict(serializers.UserSerializer(instance=user).data) + for key, value in user_values.items(): + assert value == old_user_values[key] + + +def test_api_users_delete_list_anonymous(): + """Anonymous users should not be allowed to delete a list of users.""" + factories.UserFactory.create_batch(2) + + client = APIClient() + response = client.delete("/api/v1.0/users/") + + assert response.status_code == 401 + assert models.User.objects.count() == 2 + + +def test_api_users_delete_list_authenticated(): + """Authenticated users should not be allowed to delete a list of users.""" + factories.UserFactory.create_batch(2) + user = factories.UserFactory() + + client = APIClient() + client.force_login(user) + + response = client.delete( + "/api/v1.0/users/", + ) + + assert response.status_code == 405 + assert models.User.objects.count() == 3 + + +def test_api_users_delete_anonymous(): + """Anonymous users should not be allowed to delete a user.""" + user = factories.UserFactory() + + response = APIClient().delete(f"/api/v1.0/users/{user.id!s}/") + + assert response.status_code == 401 + assert models.User.objects.count() == 1 + + +def test_api_users_delete_authenticated(): + """ + Authenticated users should not be allowed to delete a user other than themselves. + """ + user = factories.UserFactory() + + client = APIClient() + client.force_login(user) + + other_user = factories.UserFactory() + + response = client.delete( + f"/api/v1.0/users/{other_user.id!s}/", + ) + + assert response.status_code == 405 + assert models.User.objects.count() == 2 + + +def test_api_users_delete_self(): + """Authenticated users should not be able to delete their own user.""" + user = factories.UserFactory() + + client = APIClient() + client.force_login(user) + + response = client.delete( + f"/api/v1.0/users/{user.id!s}/", + ) + + assert response.status_code == 405 + assert models.User.objects.count() == 1 diff --git a/src/backend/core/tests/test_models_users.py b/src/backend/core/tests/test_models_users.py new file mode 100644 index 0000000..edea5bb --- /dev/null +++ b/src/backend/core/tests/test_models_users.py @@ -0,0 +1,46 @@ +""" +Unit tests for the User model +""" + +from unittest import mock + +from django.core.exceptions import ValidationError + +import pytest + +from core import factories + +pytestmark = pytest.mark.django_db + + +def test_models_users_str(): + """The str representation should be the email.""" + user = factories.UserFactory() + assert str(user) == user.email + + +def test_models_users_id_unique(): + """The "id" field should be unique.""" + user = factories.UserFactory() + with pytest.raises(ValidationError, match="User with this Id already exists."): + factories.UserFactory(id=user.id) + + +def test_models_users_send_mail_main_existing(): + """The "email_user' method should send mail to the user's email address.""" + user = factories.UserFactory() + + with mock.patch("django.core.mail.send_mail") as mock_send: + user.email_user("my subject", "my message") + + mock_send.assert_called_once_with("my subject", "my message", None, [user.email]) + + +def test_models_users_send_mail_main_missing(): + """The "email_user' method should fail if the user has no email address.""" + user = factories.UserFactory(email=None) + + with pytest.raises(ValueError) as excinfo: + user.email_user("my subject", "my message") + + assert str(excinfo.value) == "User has no email address." diff --git a/src/backend/core/tests/test_settings.py b/src/backend/core/tests/test_settings.py new file mode 100644 index 0000000..c65df68 --- /dev/null +++ b/src/backend/core/tests/test_settings.py @@ -0,0 +1,30 @@ +""" +Unit tests for the User model +""" + +import pytest + +from conversations.settings import Base + + +def test_invalid_settings_oidc_email_configuration(): + """ + The OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION and OIDC_ALLOW_DUPLICATE_EMAILS settings + should not be both set to True simultaneously. + """ + + class TestSettings(Base): + """Fake test settings.""" + + OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION = True + OIDC_ALLOW_DUPLICATE_EMAILS = True + + # The validation is performed during post_setup + with pytest.raises(ValueError) as excinfo: + TestSettings().post_setup() + + # Check the exception message + assert str(excinfo.value) == ( + "Both OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION and " + "OIDC_ALLOW_DUPLICATE_EMAILS cannot be set to True simultaneously. " + ) diff --git a/src/backend/core/urls.py b/src/backend/core/urls.py new file mode 100644 index 0000000..e3db1f0 --- /dev/null +++ b/src/backend/core/urls.py @@ -0,0 +1,29 @@ +"""URL configuration for the core app.""" + +from django.conf import settings +from django.urls import include, path + +from lasuite.oidc_login.urls import urlpatterns as oidc_urls +from rest_framework.routers import DefaultRouter + +from core.api import viewsets + +from chat.views import ChatViewSet + +# - Main endpoints +router = DefaultRouter() +router.register("users", viewsets.UserViewSet, basename="users") +router.register("chats", ChatViewSet, basename="chats") + +urlpatterns = [ + path( + f"api/{settings.API_VERSION}/", + include( + [ + *router.urls, + *oidc_urls, + ] + ), + ), + path(f"api/{settings.API_VERSION}/config/", viewsets.ConfigView.as_view()), +] diff --git a/src/backend/demo/__init__.py b/src/backend/demo/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/backend/demo/defaults.py b/src/backend/demo/defaults.py new file mode 100644 index 0000000..e6d9aae --- /dev/null +++ b/src/backend/demo/defaults.py @@ -0,0 +1,20 @@ +"""Parameters that define how the demo site will be built.""" + +NB_OBJECTS = { + "users": 50, +} + +DEV_USERS = [ + { + "username": "conversations", + "email": "conversations@conversations.world", + "language": "en-us", + }, + {"username": "user-e2e-webkit", "email": "user@webkit.test", "language": "en-us"}, + {"username": "user-e2e-firefox", "email": "user@firefox.test", "language": "en-us"}, + { + "username": "user-e2e-chromium", + "email": "user@chromium.test", + "language": "en-us", + }, +] diff --git a/src/backend/demo/management/__init__.py b/src/backend/demo/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/backend/demo/management/commands/__init__.py b/src/backend/demo/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/backend/demo/management/commands/create_demo.py b/src/backend/demo/management/commands/create_demo.py new file mode 100644 index 0000000..0ff7059 --- /dev/null +++ b/src/backend/demo/management/commands/create_demo.py @@ -0,0 +1,161 @@ +# ruff: noqa: S311, S106 +"""create_demo management command""" + +import logging +import math +import random +import time +from collections import defaultdict + +from django import db +from django.conf import settings +from django.core.management.base import BaseCommand, CommandError + +from faker import Faker + +from core import models + +from demo import defaults + +fake = Faker() + +logger = logging.getLogger(__name__) + + +def random_true_with_probability(probability): + """return True with the requested probability, False otherwise.""" + return random.random() < probability + + +class BulkQueue: + """A utility class to create Django model instances in bulk by just pushing to a queue.""" + + BATCH_SIZE = 20000 + + def __init__(self, stdout, *args, **kwargs): + """Define the queue as a dict of lists.""" + self.queue = defaultdict(list) + self.stdout = stdout + + def _bulk_create(self, objects): + """Actually create instances in bulk in the database.""" + if not objects: + return + + objects[0]._meta.model.objects.bulk_create(objects, ignore_conflicts=False) # noqa: SLF001 + # In debug mode, Django keeps query cache which creates a memory leak in this case + db.reset_queries() + self.queue[objects[0]._meta.model.__name__] = [] # noqa: SLF001 + + def push(self, obj): + """Add a model instance to queue to that it gets created in bulk.""" + objects = self.queue[obj._meta.model.__name__] # noqa: SLF001 + objects.append(obj) + if len(objects) > self.BATCH_SIZE: + self._bulk_create(objects) + self.stdout.write(".", ending="") + + def flush(self): + """Flush the queue after creating the remaining model instances.""" + for objects in self.queue.values(): + self._bulk_create(objects) + + +class Timeit: + """A utility context manager/method decorator to time execution.""" + + total_time = 0 + + def __init__(self, stdout, sentence=None): + """Set the sentence to be displayed for timing information.""" + self.sentence = sentence + self.start = None + self.stdout = stdout + + def __call__(self, func): + """Behavior on call for use as a method decorator.""" + + def timeit_wrapper(*args, **kwargs): + """wrapper to trigger/stop the timer before/after function call.""" + self.__enter__() + result = func(*args, **kwargs) + self.__exit__(None, None, None) + return result + + return timeit_wrapper + + def __enter__(self): + """Start timer upon entering context manager.""" + self.start = time.perf_counter() + if self.sentence: + self.stdout.write(self.sentence, ending=".") + + def __exit__(self, exc_type, exc_value, exc_tb): + """Stop timer and display result upon leaving context manager.""" + if exc_type is not None: + raise exc_type(exc_value) + end = time.perf_counter() + elapsed_time = end - self.start + if self.sentence: + self.stdout.write(f" Took {elapsed_time:g} seconds") + + self.__class__.total_time += elapsed_time + return elapsed_time + + +def create_demo(stdout): + """ + Create a database with demo data for developers to work in a realistic environment. + The code is engineered to create a huge number of objects fast. + """ + + queue = BulkQueue(stdout) + + with Timeit(stdout, "Creating users"): + name_size = int(math.sqrt(defaults.NB_OBJECTS["users"])) + first_names = [fake.first_name() for _ in range(name_size)] + last_names = [fake.last_name() for _ in range(name_size)] + for i in range(defaults.NB_OBJECTS["users"]): + first_name = random.choice(first_names) + queue.push( + models.User( + admin_email=f"user{i:d}@example.com", + email=f"user{i:d}@example.com", + password="!", + is_superuser=False, + is_active=True, + is_staff=False, + short_name=first_name, + full_name=f"{first_name:s} {random.choice(last_names):s}", + language=random.choice(settings.LANGUAGES)[0], + ) + ) + queue.flush() + + +class Command(BaseCommand): + """A management command to create a demo database.""" + + help = __doc__ + + def add_arguments(self, parser): + """Add argument to require forcing execution when not in debug mode.""" + parser.add_argument( + "-f", + "--force", + action="store_true", + default=False, + help="Force command execution despite DEBUG is set to False", + ) + + def handle(self, *args, **options): + """Handling of the management command.""" + if not settings.DEBUG and not options["force"]: + raise CommandError( + ( + "This command is not meant to be used in production environment " + "except you know what you are doing, if so use --force parameter" + ) + ) + + create_demo(self.stdout) diff --git a/src/backend/demo/management/commands/createsuperuser.py b/src/backend/demo/management/commands/createsuperuser.py new file mode 100644 index 0000000..9c5ab78 --- /dev/null +++ b/src/backend/demo/management/commands/createsuperuser.py @@ -0,0 +1,47 @@ +"""Management user to create a superuser.""" + +from django.contrib.auth import get_user_model +from django.core.management.base import BaseCommand + +UserModel = get_user_model() + + +class Command(BaseCommand): + """Management command to create a superuser from and email and password.""" + + help = "Create a superuser with an email and a password" + + def add_arguments(self, parser): + """Define required arguments "email" and "password".""" + parser.add_argument( + "--email", + help=("Email for the user."), + ) + parser.add_argument( + "--password", + help="Password for the user.", + ) + + def handle(self, *args, **options): + """ + Given an email and a password, create a superuser or upgrade the existing + user to superuser status. + """ + email = options.get("email") + try: + user = UserModel.objects.get(admin_email=email) + except UserModel.DoesNotExist: + user = UserModel(admin_email=email) + message = "Superuser created successfully." + else: + if user.is_superuser and user.is_staff: + message = "Superuser already exists." + else: + message = "User already existed and was upgraded to superuser." + + user.is_superuser = True + user.is_staff = True + user.set_password(options["password"]) + user.save() + + self.stdout.write(self.style.SUCCESS(message)) diff --git a/src/backend/demo/tests/__init__.py b/src/backend/demo/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/backend/demo/tests/test_commands_create_demo.py b/src/backend/demo/tests/test_commands_create_demo.py new file mode 100644 index 0000000..2519d6b --- /dev/null +++ b/src/backend/demo/tests/test_commands_create_demo.py @@ -0,0 +1,26 @@ +"""Test the `create_demo` management command""" + +from unittest import mock + +from django.core.management import call_command +from django.test import override_settings + +import pytest + +from core import models + +pytestmark = pytest.mark.django_db + + +@mock.patch( + "demo.defaults.NB_OBJECTS", + { + "users": 10, + }, +) +@override_settings(DEBUG=True) +def test_commands_create_demo(): + """The create_demo management command should create objects as expected.""" + call_command("create_demo") + + assert models.User.objects.count() == 10 diff --git a/src/backend/locale/br_FR/LC_MESSAGES/django.po b/src/backend/locale/br_FR/LC_MESSAGES/django.po new file mode 100644 index 0000000..a13ecec --- /dev/null +++ b/src/backend/locale/br_FR/LC_MESSAGES/django.po @@ -0,0 +1,390 @@ +msgid "" +msgstr "" +"Project-Id-Version: lasuite-docs\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-05-22 12:09+0000\n" +"PO-Revision-Date: 2025-05-22 14:16\n" +"Last-Translator: \n" +"Language-Team: Breton\n" +"Language: br_FR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=5; plural=(n%10==1 && (n%100!=11 || n%100!=71 || n%100!=91) ? 0 : n%10==2 && (n%100!=12 || n%100!=72 || n%100!=92) ? 1 : ((n%10>=3 && n%10<=4) || n%10==9) && ((n%100 < 10 || n%100 > 19) || (n%100 < 70 || n%100 > 79) || (n%100 < 90 || n%100 > 99)) ? 2 : (n!=0 && n%1;\n" +"X-Crowdin-Project: lasuite-docs\n" +"X-Crowdin-Project-ID: 754523\n" +"X-Crowdin-Language: br-FR\n" +"X-Crowdin-File: backend-conversations.pot\n" +"X-Crowdin-File-ID: 18\n" + +#: build/lib/core/admin.py:37 core/admin.py:37 +msgid "Personal info" +msgstr "Titouroรน personel" + +#: build/lib/core/admin.py:50 build/lib/core/admin.py:138 core/admin.py:50 +#: core/admin.py:138 +msgid "Permissions" +msgstr "Aotreoรน" + +#: build/lib/core/admin.py:62 core/admin.py:62 +msgid "Important dates" +msgstr "Deiziadoรน a-bouez" + +#: build/lib/core/admin.py:148 core/admin.py:148 +msgid "Tree structure" +msgstr "Gwezennadur" + +#: build/lib/core/api/filters.py:47 core/api/filters.py:47 +msgid "Title" +msgstr "Titl" + +#: build/lib/core/api/filters.py:61 core/api/filters.py:61 +msgid "Creator is me" +msgstr "Me eo an aozer" + +#: build/lib/core/api/filters.py:64 core/api/filters.py:64 +msgid "Favorite" +msgstr "Sinedoรน" + +#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446 +msgid "A new document was created on your behalf!" +msgstr "Ur restr nevez a zo bet krouet ganeoc'h!" + +#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450 +msgid "You have been granted ownership of a new document:" +msgstr "C'hwi zo bet disklaeriet perc'henn ur restr nevez:" + +#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586 +msgid "Body" +msgstr "Korf" + +#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589 +msgid "Body type" +msgstr "Doare korf" + +#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595 +msgid "Format" +msgstr "Stumm" + +#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967 +#, python-brace-format +msgid "copy of {title}" +msgstr "eilenn {title}" + +#: build/lib/core/enums.py:36 core/enums.py:36 +msgid "First child" +msgstr "Bugel kentaรฑ" + +#: build/lib/core/enums.py:37 core/enums.py:37 +msgid "Last child" +msgstr "Bugel diwezhaรฑ" + +#: build/lib/core/enums.py:38 core/enums.py:38 +msgid "First sibling" +msgstr "" + +#: build/lib/core/enums.py:39 core/enums.py:39 +msgid "Last sibling" +msgstr "" + +#: build/lib/core/enums.py:40 core/enums.py:40 +msgid "Left" +msgstr "Kleiz" + +#: build/lib/core/enums.py:41 core/enums.py:41 +msgid "Right" +msgstr "Dehoรน" + +#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56 +#: core/models.py:63 +msgid "Reader" +msgstr "Lenner" + +#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57 +#: core/models.py:64 +msgid "Editor" +msgstr "" + +#: build/lib/core/models.py:65 core/models.py:65 +msgid "Administrator" +msgstr "Merour" + +#: build/lib/core/models.py:66 core/models.py:66 +msgid "Owner" +msgstr "Perc'henn" + +#: build/lib/core/models.py:77 core/models.py:77 +msgid "Restricted" +msgstr "" + +#: build/lib/core/models.py:81 core/models.py:81 +msgid "Authenticated" +msgstr "" + +#: build/lib/core/models.py:83 core/models.py:83 +msgid "Public" +msgstr "Publik" + +#: build/lib/core/models.py:154 core/models.py:154 +msgid "id" +msgstr "id" + +#: build/lib/core/models.py:155 core/models.py:155 +msgid "primary key for the record as UUID" +msgstr "" + +#: build/lib/core/models.py:161 core/models.py:161 +msgid "created on" +msgstr "krouet d'ar/al" + +#: build/lib/core/models.py:162 core/models.py:162 +msgid "date and time at which a record was created" +msgstr "" + +#: build/lib/core/models.py:167 core/models.py:167 +msgid "updated on" +msgstr "hizivaet d'ar/al" + +#: build/lib/core/models.py:168 core/models.py:168 +msgid "date and time at which a record was last updated" +msgstr "" + +#: build/lib/core/models.py:204 core/models.py:204 +msgid "We couldn't find a user with this sub but the email is already associated with a registered user." +msgstr "" + +#: build/lib/core/models.py:217 core/models.py:217 +msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters." +msgstr "" + +#: build/lib/core/models.py:223 core/models.py:223 +msgid "sub" +msgstr "" + +#: build/lib/core/models.py:225 core/models.py:225 +msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only." +msgstr "" + +#: build/lib/core/models.py:234 core/models.py:234 +msgid "full name" +msgstr "anv klok" + +#: build/lib/core/models.py:235 core/models.py:235 +msgid "short name" +msgstr "anv berr" + +#: build/lib/core/models.py:237 core/models.py:237 +msgid "identity email address" +msgstr "" + +#: build/lib/core/models.py:242 core/models.py:242 +msgid "admin email address" +msgstr "" + +#: build/lib/core/models.py:249 core/models.py:249 +msgid "language" +msgstr "yezh" + +#: build/lib/core/models.py:250 core/models.py:250 +msgid "The language in which the user wants to see the interface." +msgstr "" + +#: build/lib/core/models.py:258 core/models.py:258 +msgid "The timezone in which the user wants to see times." +msgstr "" + +#: build/lib/core/models.py:261 core/models.py:261 +msgid "device" +msgstr "trevnad" + +#: build/lib/core/models.py:263 core/models.py:263 +msgid "Whether the user is a device or a real user." +msgstr "" + +#: build/lib/core/models.py:266 core/models.py:266 +msgid "staff status" +msgstr "" + +#: build/lib/core/models.py:268 core/models.py:268 +msgid "Whether the user can log into this admin site." +msgstr "" + +#: build/lib/core/models.py:271 core/models.py:271 +msgid "active" +msgstr "" + +#: build/lib/core/models.py:274 core/models.py:274 +msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts." +msgstr "" + +#: build/lib/core/models.py:286 core/models.py:286 +msgid "user" +msgstr "implijer" + +#: build/lib/core/models.py:287 core/models.py:287 +msgid "users" +msgstr "implijerien" + +#: build/lib/core/models.py:470 build/lib/core/models.py:1155 +#: core/models.py:470 core/models.py:1155 +msgid "title" +msgstr "titl" + +#: build/lib/core/models.py:471 core/models.py:471 +msgid "excerpt" +msgstr "" + +#: build/lib/core/models.py:519 core/models.py:519 +msgid "Document" +msgstr "" + +#: build/lib/core/models.py:520 core/models.py:520 +msgid "Documents" +msgstr "" + +#: build/lib/core/models.py:532 build/lib/core/models.py:873 core/models.py:532 +#: core/models.py:873 +msgid "Untitled Document" +msgstr "" + +#: build/lib/core/models.py:908 core/models.py:908 +#, python-brace-format +msgid "{name} shared a document with you!" +msgstr "" + +#: build/lib/core/models.py:912 core/models.py:912 +#, python-brace-format +msgid "{name} invited you with the role \"{role}\" on the following document:" +msgstr "" + +#: build/lib/core/models.py:918 core/models.py:918 +#, python-brace-format +msgid "{name} shared a document with you: {title}" +msgstr "" + +#: build/lib/core/models.py:1016 core/models.py:1016 +msgid "Document/user link trace" +msgstr "" + +#: build/lib/core/models.py:1017 core/models.py:1017 +msgid "Document/user link traces" +msgstr "" + +#: build/lib/core/models.py:1023 core/models.py:1023 +msgid "A link trace already exists for this document/user." +msgstr "" + +#: build/lib/core/models.py:1046 core/models.py:1046 +msgid "Document favorite" +msgstr "" + +#: build/lib/core/models.py:1047 core/models.py:1047 +msgid "Document favorites" +msgstr "" + +#: build/lib/core/models.py:1053 core/models.py:1053 +msgid "This document is already targeted by a favorite relation instance for the same user." +msgstr "" + +#: build/lib/core/models.py:1075 core/models.py:1075 +msgid "Document/user relation" +msgstr "" + +#: build/lib/core/models.py:1076 core/models.py:1076 +msgid "Document/user relations" +msgstr "" + +#: build/lib/core/models.py:1082 core/models.py:1082 +msgid "This user is already in this document." +msgstr "" + +#: build/lib/core/models.py:1088 core/models.py:1088 +msgid "This team is already in this document." +msgstr "" + +#: build/lib/core/models.py:1094 build/lib/core/models.py:1242 +#: core/models.py:1094 core/models.py:1242 +msgid "Either user or team must be set, not both." +msgstr "" + +#: build/lib/core/models.py:1156 core/models.py:1156 +msgid "description" +msgstr "" + +#: build/lib/core/models.py:1157 core/models.py:1157 +msgid "code" +msgstr "" + +#: build/lib/core/models.py:1158 core/models.py:1158 +msgid "css" +msgstr "css" + +#: build/lib/core/models.py:1160 core/models.py:1160 +msgid "public" +msgstr "publik" + +#: build/lib/core/models.py:1162 core/models.py:1162 +msgid "Whether this template is public for anyone to use." +msgstr "" + +#: build/lib/core/models.py:1168 core/models.py:1168 +msgid "Template" +msgstr "Patrom" + +#: build/lib/core/models.py:1169 core/models.py:1169 +msgid "Templates" +msgstr "Patromoรน" + +#: build/lib/core/models.py:1223 core/models.py:1223 +msgid "Template/user relation" +msgstr "" + +#: build/lib/core/models.py:1224 core/models.py:1224 +msgid "Template/user relations" +msgstr "" + +#: build/lib/core/models.py:1230 core/models.py:1230 +msgid "This user is already in this template." +msgstr "" + +#: build/lib/core/models.py:1236 core/models.py:1236 +msgid "This team is already in this template." +msgstr "" + +#: build/lib/core/models.py:1259 core/models.py:1259 +msgid "email address" +msgstr "" + +#: build/lib/core/models.py:1278 core/models.py:1278 +msgid "Document invitation" +msgstr "" + +#: build/lib/core/models.py:1279 core/models.py:1279 +msgid "Document invitations" +msgstr "" + +#: build/lib/core/models.py:1299 core/models.py:1299 +msgid "This email is already associated to a registered user." +msgstr "" + +#: core/templates/mail/html/invitation.html:162 +#: core/templates/mail/text/invitation.txt:3 +msgid "Logo email" +msgstr "" + +#: core/templates/mail/html/invitation.html:209 +#: core/templates/mail/text/invitation.txt:10 +msgid "Open" +msgstr "Digeriรฑ" + +#: core/templates/mail/html/invitation.html:226 +#: core/templates/mail/text/invitation.txt:14 +msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. " +msgstr "" + +#: core/templates/mail/html/invitation.html:233 +#: core/templates/mail/text/invitation.txt:16 +#, python-format +msgid " Brought to you by %(brandname)s " +msgstr "" + diff --git a/src/backend/locale/cn_CN/LC_MESSAGES/django.po b/src/backend/locale/cn_CN/LC_MESSAGES/django.po new file mode 100644 index 0000000..b1ef36f --- /dev/null +++ b/src/backend/locale/cn_CN/LC_MESSAGES/django.po @@ -0,0 +1,399 @@ +msgid "" +msgstr "" +"Project-Id-Version: lasuite-docs\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-04-04 13:46+0000\n" +"PO-Revision-Date: 2025-04-16 16:32\n" +"Last-Translator: \n" +"Language-Team: Chinese Simplified\n" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Crowdin-Project: lasuite-docs\n" +"X-Crowdin-Project-ID: 754523\n" +"X-Crowdin-Language: zh-CN\n" +"X-Crowdin-File: backend-conversations.pot\n" +"X-Crowdin-File-ID: 18\n" + +#: build/lib/core/admin.py:37 core/admin.py:37 +msgid "Personal info" +msgstr "ไธชไบบไฟกๆฏ" + +#: build/lib/core/admin.py:50 build/lib/core/admin.py:138 core/admin.py:50 +#: core/admin.py:138 +msgid "Permissions" +msgstr "ๆƒ้™" + +#: build/lib/core/admin.py:62 core/admin.py:62 +msgid "Important dates" +msgstr "้‡่ฆๆ—ฅๆœŸ" + +#: build/lib/core/admin.py:148 core/admin.py:148 +msgid "Tree structure" +msgstr "ๆ ‘็Šถ็ป“ๆž„" + +#: build/lib/core/api/filters.py:16 core/api/filters.py:16 +msgid "Title" +msgstr "ๆ ‡้ข˜" + +#: build/lib/core/api/filters.py:30 core/api/filters.py:30 +msgid "Creator is me" +msgstr "ๅˆ›ๅปบ่€…ๆ˜ฏๆˆ‘" + +#: build/lib/core/api/filters.py:33 core/api/filters.py:33 +msgid "Favorite" +msgstr "ๆ”ถ่—" + +#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446 +msgid "A new document was created on your behalf!" +msgstr "ๅทฒไธบๆ‚จๅˆ›ๅปบไบ†ไธ€ไปฝๆ–ฐๆ–‡ๆกฃ๏ผ" + +#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450 +msgid "You have been granted ownership of a new document:" +msgstr "ๆ‚จๅทฒ่ขซๆŽˆไบˆๆ–ฐๆ–‡ๆกฃ็š„ๆ‰€ๆœ‰ๆƒ๏ผš" + +#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586 +msgid "Body" +msgstr "ๆญฃๆ–‡" + +#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589 +msgid "Body type" +msgstr "ๆญฃๆ–‡็ฑปๅž‹" + +#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595 +msgid "Format" +msgstr "ๆ ผๅผ" + +#: build/lib/core/api/viewsets.py:944 core/api/viewsets.py:944 +#, python-brace-format +msgid "copy of {title}" +msgstr "{title} ็š„ๅ‰ฏๆœฌ" + +#: build/lib/core/authentication/backends.py:61 +#: core/authentication/backends.py:61 +msgid "Invalid response format or token verification failed" +msgstr "ๅ“ๅบ”ๆ ผๅผๆ— ๆ•ˆๆˆ–ไปค็‰Œ้ชŒ่ฏๅคฑ่ดฅ" + +#: build/lib/core/authentication/backends.py:108 +#: core/authentication/backends.py:108 +msgid "User account is disabled" +msgstr "็”จๆˆท่ดฆๆˆทๅทฒ่ขซ็ฆ็”จ" + +#: build/lib/core/enums.py:35 core/enums.py:35 +msgid "First child" +msgstr "็ฌฌไธ€ไธชๅญ้กน" + +#: build/lib/core/enums.py:36 core/enums.py:36 +msgid "Last child" +msgstr "ๆœ€ๅŽไธ€ไธชๅญ้กน" + +#: build/lib/core/enums.py:37 core/enums.py:37 +msgid "First sibling" +msgstr "็ฌฌไธ€ไธชๅŒ็บง้กน" + +#: build/lib/core/enums.py:38 core/enums.py:38 +msgid "Last sibling" +msgstr "ๆœ€ๅŽไธ€ไธชๅŒ็บง้กน" + +#: build/lib/core/enums.py:39 core/enums.py:39 +msgid "Left" +msgstr "ๅทฆ" + +#: build/lib/core/enums.py:40 core/enums.py:40 +msgid "Right" +msgstr "ๅณ" + +#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56 +#: core/models.py:63 +msgid "Reader" +msgstr "้˜…่ฏป่€…" + +#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57 +#: core/models.py:64 +msgid "Editor" +msgstr "็ผ–่พ‘่€…" + +#: build/lib/core/models.py:65 core/models.py:65 +msgid "Administrator" +msgstr "่ถ…็บง็ฎก็†ๅ‘˜" + +#: build/lib/core/models.py:66 core/models.py:66 +msgid "Owner" +msgstr "ๆ‰€ๆœ‰่€…" + +#: build/lib/core/models.py:77 core/models.py:77 +msgid "Restricted" +msgstr "ๅ—้™็š„" + +#: build/lib/core/models.py:81 core/models.py:81 +msgid "Authenticated" +msgstr "ๅทฒ้ชŒ่ฏ" + +#: build/lib/core/models.py:83 core/models.py:83 +msgid "Public" +msgstr "ๅ…ฌๅผ€" + +#: build/lib/core/models.py:154 core/models.py:154 +msgid "id" +msgstr "id" + +#: build/lib/core/models.py:155 core/models.py:155 +msgid "primary key for the record as UUID" +msgstr "่ฎฐๅฝ•็š„ไธปๅฏ†้’ฅไธบ UUID" + +#: build/lib/core/models.py:161 core/models.py:161 +msgid "created on" +msgstr "ๅˆ›ๅปบๆ—ถ้—ด" + +#: build/lib/core/models.py:162 core/models.py:162 +msgid "date and time at which a record was created" +msgstr "่ฎฐๅฝ•็š„ๅˆ›ๅปบๆ—ฅๆœŸๅ’Œๆ—ถ้—ด" + +#: build/lib/core/models.py:167 core/models.py:167 +msgid "updated on" +msgstr "ๆ›ดๆ–ฐๆ—ถ้—ด" + +#: build/lib/core/models.py:168 core/models.py:168 +msgid "date and time at which a record was last updated" +msgstr "่ฎฐๅฝ•็š„ๆœ€ๅŽๆ›ดๆ–ฐๆ—ถ้—ด" + +#: build/lib/core/models.py:204 core/models.py:204 +msgid "We couldn't find a user with this sub but the email is already associated with a registered user." +msgstr "ๆœชๆ‰พๅˆฐๅ…ทๆœ‰่ฏฅ sub ็š„็”จๆˆท๏ผŒไฝ†่ฏฅ้‚ฎ็ฎฑๅทฒๅ…ณ่”ๅˆฐไธ€ไธชๆณจๅ†Œ็”จๆˆทใ€‚" + +#: build/lib/core/models.py:217 core/models.py:217 +msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters." +msgstr "่ฏท่พ“ๅ…ฅๆœ‰ๆ•ˆ็š„ subใ€‚่ฏฅๅ€ผๅช่ƒฝๅŒ…ๅซๅญ—ๆฏใ€ๆ•ฐๅญ—ๅŠ @/./+/-/_/: ๅญ—็ฌฆใ€‚" + +#: build/lib/core/models.py:223 core/models.py:223 +msgid "sub" +msgstr "sub" + +#: build/lib/core/models.py:225 core/models.py:225 +msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only." +msgstr "ๅฟ…ๅกซใ€‚ๆœ€ๅคš 255 ไธชๅญ—็ฌฆ๏ผŒไป…ๅ…่ฎธๅญ—ๆฏใ€ๆ•ฐๅญ—ๅŠ @/./+/-/_/: ๅญ—็ฌฆใ€‚" + +#: build/lib/core/models.py:234 core/models.py:234 +msgid "full name" +msgstr "ๅ…จๅ" + +#: build/lib/core/models.py:235 core/models.py:235 +msgid "short name" +msgstr "็ฎ€็งฐ" + +#: build/lib/core/models.py:237 core/models.py:237 +msgid "identity email address" +msgstr "่บซไปฝ็”ตๅญ้‚ฎไปถๅœฐๅ€" + +#: build/lib/core/models.py:242 core/models.py:242 +msgid "admin email address" +msgstr "็ฎก็†ๅ‘˜็”ตๅญ้‚ฎไปถๅœฐๅ€" + +#: build/lib/core/models.py:249 core/models.py:249 +msgid "language" +msgstr "่ฏญ่จ€" + +#: build/lib/core/models.py:250 core/models.py:250 +msgid "The language in which the user wants to see the interface." +msgstr "็”จๆˆทๅธŒๆœ›็œ‹ๅˆฐ็š„็•Œ้ข่ฏญ่จ€ใ€‚" + +#: build/lib/core/models.py:258 core/models.py:258 +msgid "The timezone in which the user wants to see times." +msgstr "็”จๆˆทๆŸฅ็œ‹ๆ—ถ้—ดๅธŒๆœ›็š„ๆ—ถๅŒบใ€‚" + +#: build/lib/core/models.py:261 core/models.py:261 +msgid "device" +msgstr "่ฎพๅค‡" + +#: build/lib/core/models.py:263 core/models.py:263 +msgid "Whether the user is a device or a real user." +msgstr "็”จๆˆทๆ˜ฏ่ฎพๅค‡่ฟ˜ๆ˜ฏ็œŸๅฎž็”จๆˆทใ€‚" + +#: build/lib/core/models.py:266 core/models.py:266 +msgid "staff status" +msgstr "ๅ‘˜ๅทฅ็Šถๆ€" + +#: build/lib/core/models.py:268 core/models.py:268 +msgid "Whether the user can log into this admin site." +msgstr "็”จๆˆทๆ˜ฏๅฆๅฏไปฅ็™ปๅฝ•่ฏฅ็ฎก็†ๅ‘˜็ซ™็‚นใ€‚" + +#: build/lib/core/models.py:271 core/models.py:271 +msgid "active" +msgstr "ๆฟ€ๆดป" + +#: build/lib/core/models.py:274 core/models.py:274 +msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts." +msgstr "ๆ˜ฏๅฆๅบ”ๅฐ†ๆญค็”จๆˆท่ง†ไธบๆดป่ทƒ็”จๆˆทใ€‚ๅ–ๆถˆ้€‰ๆ‹ฉๆญค้€‰้กน่€Œไธๆ˜ฏๅˆ ้™ค่ดฆๆˆทใ€‚" + +#: build/lib/core/models.py:286 core/models.py:286 +msgid "user" +msgstr "็”จๆˆท" + +#: build/lib/core/models.py:287 core/models.py:287 +msgid "users" +msgstr "ไธช็”จๆˆท" + +#: build/lib/core/models.py:470 build/lib/core/models.py:1154 +#: core/models.py:470 core/models.py:1154 +msgid "title" +msgstr "ๆ ‡้ข˜" + +#: build/lib/core/models.py:471 core/models.py:471 +msgid "excerpt" +msgstr "ๆ‘˜่ฆ" + +#: build/lib/core/models.py:519 core/models.py:519 +msgid "Document" +msgstr "ๆ–‡ๆกฃ" + +#: build/lib/core/models.py:520 core/models.py:520 +msgid "Documents" +msgstr "ไธชๆ–‡ๆกฃ" + +#: build/lib/core/models.py:532 build/lib/core/models.py:872 core/models.py:532 +#: core/models.py:872 +msgid "Untitled Document" +msgstr "ๆœชๅ‘ฝๅๆ–‡ๆกฃ" + +#: build/lib/core/models.py:907 core/models.py:907 +#, python-brace-format +msgid "{name} shared a document with you!" +msgstr "{name} ไธŽๆ‚จๅ…ฑไบซไบ†ไธ€ไธชๆ–‡ๆกฃ๏ผ" + +#: build/lib/core/models.py:911 core/models.py:911 +#, python-brace-format +msgid "{name} invited you with the role \"{role}\" on the following document:" +msgstr "{name} ้‚€่ฏทๆ‚จไปฅโ€œ{role}โ€่ง’่‰ฒ่ฎฟ้—ฎไปฅไธ‹ๆ–‡ๆกฃ๏ผš" + +#: build/lib/core/models.py:917 core/models.py:917 +#, python-brace-format +msgid "{name} shared a document with you: {title}" +msgstr "{name} ไธŽๆ‚จๅ…ฑไบซไบ†ไธ€ไธชๆ–‡ๆกฃ๏ผš{title}" + +#: build/lib/core/models.py:1015 core/models.py:1015 +msgid "Document/user link trace" +msgstr "ๆ–‡ๆกฃ/็”จๆˆท้“พๆŽฅ่ทŸ่ธช" + +#: build/lib/core/models.py:1016 core/models.py:1016 +msgid "Document/user link traces" +msgstr "ไธชๆ–‡ๆกฃ/็”จๆˆท้“พๆŽฅ่ทŸ่ธช" + +#: build/lib/core/models.py:1022 core/models.py:1022 +msgid "A link trace already exists for this document/user." +msgstr "ๆญคๆ–‡ๆกฃ/็”จๆˆท็š„้“พๆŽฅ่ทŸ่ธชๅทฒๅญ˜ๅœจใ€‚" + +#: build/lib/core/models.py:1045 core/models.py:1045 +msgid "Document favorite" +msgstr "ๆ–‡ๆกฃๆ”ถ่—" + +#: build/lib/core/models.py:1046 core/models.py:1046 +msgid "Document favorites" +msgstr "ๆ–‡ๆกฃๆ”ถ่—ๅคน" + +#: build/lib/core/models.py:1052 core/models.py:1052 +msgid "This document is already targeted by a favorite relation instance for the same user." +msgstr "่ฏฅๆ–‡ๆกฃๅทฒ่ขซๅŒไธ€็”จๆˆท็š„ๆ”ถ่—ๅ…ณ็ณปๅฎžไพ‹ๅ…ณ่”ใ€‚" + +#: build/lib/core/models.py:1074 core/models.py:1074 +msgid "Document/user relation" +msgstr "ๆ–‡ๆกฃ/็”จๆˆทๅ…ณ็ณป" + +#: build/lib/core/models.py:1075 core/models.py:1075 +msgid "Document/user relations" +msgstr "ๆ–‡ๆกฃ/็”จๆˆทๅ…ณ็ณป้›†" + +#: build/lib/core/models.py:1081 core/models.py:1081 +msgid "This user is already in this document." +msgstr "่ฏฅ็”จๆˆทๅทฒๅœจๆญคๆ–‡ๆกฃไธญใ€‚" + +#: build/lib/core/models.py:1087 core/models.py:1087 +msgid "This team is already in this document." +msgstr "่ฏฅๅ›ข้˜Ÿๅทฒๅœจๆญคๆ–‡ๆกฃไธญใ€‚" + +#: build/lib/core/models.py:1093 build/lib/core/models.py:1241 +#: core/models.py:1093 core/models.py:1241 +msgid "Either user or team must be set, not both." +msgstr "ๅฟ…้กป่ฎพ็ฝฎ็”จๆˆทๆˆ–ๅ›ข้˜Ÿไน‹ไธ€๏ผŒไธ่ƒฝๅŒๆ—ถ่ฎพ็ฝฎไธค่€…ใ€‚" + +#: build/lib/core/models.py:1155 core/models.py:1155 +msgid "description" +msgstr "่ฏดๆ˜Ž" + +#: build/lib/core/models.py:1156 core/models.py:1156 +msgid "code" +msgstr "ไปฃ็ " + +#: build/lib/core/models.py:1157 core/models.py:1157 +msgid "css" +msgstr "css" + +#: build/lib/core/models.py:1159 core/models.py:1159 +msgid "public" +msgstr "ๅ…ฌๅผ€" + +#: build/lib/core/models.py:1161 core/models.py:1161 +msgid "Whether this template is public for anyone to use." +msgstr "่ฏฅๆจกๆฟๆ˜ฏๅฆๅ…ฌๅผ€ไพ›ไปปไฝ•ไบบไฝฟ็”จใ€‚" + +#: build/lib/core/models.py:1167 core/models.py:1167 +msgid "Template" +msgstr "ๆจกๆฟ" + +#: build/lib/core/models.py:1168 core/models.py:1168 +msgid "Templates" +msgstr "ๆจกๆฟ" + +#: build/lib/core/models.py:1222 core/models.py:1222 +msgid "Template/user relation" +msgstr "ๆจกๆฟ/็”จๆˆทๅ…ณ็ณป" + +#: build/lib/core/models.py:1223 core/models.py:1223 +msgid "Template/user relations" +msgstr "ๆจกๆฟ/็”จๆˆทๅ…ณ็ณป้›†" + +#: build/lib/core/models.py:1229 core/models.py:1229 +msgid "This user is already in this template." +msgstr "่ฏฅ็”จๆˆทๅทฒๅœจๆญคๆจกๆฟไธญใ€‚" + +#: build/lib/core/models.py:1235 core/models.py:1235 +msgid "This team is already in this template." +msgstr "่ฏฅๅ›ข้˜Ÿๅทฒๅœจๆญคๆจกๆฟไธญใ€‚" + +#: build/lib/core/models.py:1258 core/models.py:1258 +msgid "email address" +msgstr "็”ตๅญ้‚ฎไปถๅœฐๅ€" + +#: build/lib/core/models.py:1277 core/models.py:1277 +msgid "Document invitation" +msgstr "ๆ–‡ๆกฃ้‚€่ฏท" + +#: build/lib/core/models.py:1278 core/models.py:1278 +msgid "Document invitations" +msgstr "ๆ–‡ๆกฃ้‚€่ฏท" + +#: build/lib/core/models.py:1298 core/models.py:1298 +msgid "This email is already associated to a registered user." +msgstr "ๆญค็”ตๅญ้‚ฎไปถๅทฒ็ปไธŽ็Žฐๆœ‰ๆณจๅ†Œ็”จๆˆทๅ…ณ่”ใ€‚" + +#: core/templates/mail/html/invitation.html:162 +#: core/templates/mail/text/invitation.txt:3 +msgid "Logo email" +msgstr "ๅพฝๆ ‡้‚ฎไปถ" + +#: core/templates/mail/html/invitation.html:209 +#: core/templates/mail/text/invitation.txt:10 +msgid "Open" +msgstr "ๆ‰“ๅผ€" + +#: core/templates/mail/html/invitation.html:226 +#: core/templates/mail/text/invitation.txt:14 +msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. " +msgstr " Docsโ€”โ€”ๆ‚จ็š„ๅ…จๆ–ฐๅฟ…ๅค‡ๅทฅๅ…ท๏ผŒๅธฎๅŠฉๅ›ข้˜Ÿ็ป„็ป‡ใ€ๅ…ฑไบซๅ’Œๅไฝœๅค„็†ๆ–‡ๆกฃใ€‚ " + +#: core/templates/mail/html/invitation.html:233 +#: core/templates/mail/text/invitation.txt:16 +#, python-format +msgid " Brought to you by %(brandname)s " +msgstr " ็”ฑ %(brandname)s ๅ€พๅŠ›ๆ‰“้€ ใ€‚ " diff --git a/src/backend/locale/de_DE/LC_MESSAGES/django.po b/src/backend/locale/de_DE/LC_MESSAGES/django.po new file mode 100644 index 0000000..f5f1e99 --- /dev/null +++ b/src/backend/locale/de_DE/LC_MESSAGES/django.po @@ -0,0 +1,390 @@ +msgid "" +msgstr "" +"Project-Id-Version: lasuite-docs\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-05-22 12:09+0000\n" +"PO-Revision-Date: 2025-05-22 14:16\n" +"Last-Translator: \n" +"Language-Team: German\n" +"Language: de_DE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Crowdin-Project: lasuite-docs\n" +"X-Crowdin-Project-ID: 754523\n" +"X-Crowdin-Language: de\n" +"X-Crowdin-File: backend-conversations.pot\n" +"X-Crowdin-File-ID: 18\n" + +#: build/lib/core/admin.py:37 core/admin.py:37 +msgid "Personal info" +msgstr "Persรถnliche Daten" + +#: build/lib/core/admin.py:50 build/lib/core/admin.py:138 core/admin.py:50 +#: core/admin.py:138 +msgid "Permissions" +msgstr "Berechtigungen" + +#: build/lib/core/admin.py:62 core/admin.py:62 +msgid "Important dates" +msgstr "Wichtige Daten" + +#: build/lib/core/admin.py:148 core/admin.py:148 +msgid "Tree structure" +msgstr "Baumstruktur" + +#: build/lib/core/api/filters.py:47 core/api/filters.py:47 +msgid "Title" +msgstr "Titel" + +#: build/lib/core/api/filters.py:61 core/api/filters.py:61 +msgid "Creator is me" +msgstr "Ersteller bin ich" + +#: build/lib/core/api/filters.py:64 core/api/filters.py:64 +msgid "Favorite" +msgstr "Favorit" + +#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446 +msgid "A new document was created on your behalf!" +msgstr "Ein neues Dokument wurde in Ihrem Namen erstellt!" + +#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450 +msgid "You have been granted ownership of a new document:" +msgstr "Sie sind Besitzer eines neuen Dokuments:" + +#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586 +msgid "Body" +msgstr "Inhalt" + +#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589 +msgid "Body type" +msgstr "Typ" + +#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595 +msgid "Format" +msgstr "Format" + +#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967 +#, python-brace-format +msgid "copy of {title}" +msgstr "Kopie von {title}" + +#: build/lib/core/enums.py:36 core/enums.py:36 +msgid "First child" +msgstr "Erstes Unterelement" + +#: build/lib/core/enums.py:37 core/enums.py:37 +msgid "Last child" +msgstr "Letztes Unterelement" + +#: build/lib/core/enums.py:38 core/enums.py:38 +msgid "First sibling" +msgstr "Erstes Nebenelement" + +#: build/lib/core/enums.py:39 core/enums.py:39 +msgid "Last sibling" +msgstr "Letztes Nebenelement" + +#: build/lib/core/enums.py:40 core/enums.py:40 +msgid "Left" +msgstr "Links" + +#: build/lib/core/enums.py:41 core/enums.py:41 +msgid "Right" +msgstr "Rechts" + +#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56 +#: core/models.py:63 +msgid "Reader" +msgstr "Lesen" + +#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57 +#: core/models.py:64 +msgid "Editor" +msgstr "Bearbeiten" + +#: build/lib/core/models.py:65 core/models.py:65 +msgid "Administrator" +msgstr "Administrator" + +#: build/lib/core/models.py:66 core/models.py:66 +msgid "Owner" +msgstr "Besitzer" + +#: build/lib/core/models.py:77 core/models.py:77 +msgid "Restricted" +msgstr "Beschrรคnkt" + +#: build/lib/core/models.py:81 core/models.py:81 +msgid "Authenticated" +msgstr "Authentifiziert" + +#: build/lib/core/models.py:83 core/models.py:83 +msgid "Public" +msgstr "ร–ffentlich" + +#: build/lib/core/models.py:154 core/models.py:154 +msgid "id" +msgstr "id" + +#: build/lib/core/models.py:155 core/models.py:155 +msgid "primary key for the record as UUID" +msgstr "primรคrer Schlรผssel fรผr den Datensatz als UUID" + +#: build/lib/core/models.py:161 core/models.py:161 +msgid "created on" +msgstr "Erstellt" + +#: build/lib/core/models.py:162 core/models.py:162 +msgid "date and time at which a record was created" +msgstr "Datum und Uhrzeit, an dem ein Datensatz erstellt wurde" + +#: build/lib/core/models.py:167 core/models.py:167 +msgid "updated on" +msgstr "Aktualisiert" + +#: build/lib/core/models.py:168 core/models.py:168 +msgid "date and time at which a record was last updated" +msgstr "Datum und Uhrzeit, an dem zuletzt aktualisiert wurde" + +#: build/lib/core/models.py:204 core/models.py:204 +msgid "We couldn't find a user with this sub but the email is already associated with a registered user." +msgstr "Wir konnten keinen Benutzer mit diesem Abo finden, aber die E-Mail-Adresse ist bereits einem registrierten Benutzer zugeordnet." + +#: build/lib/core/models.py:217 core/models.py:217 +msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters." +msgstr "Geben Sie eine gรผltige Unterseite ein. Dieser Wert darf nur Buchstaben, Zahlen und die @/./+/-/_/: Zeichen enthalten." + +#: build/lib/core/models.py:223 core/models.py:223 +msgid "sub" +msgstr "unter" + +#: build/lib/core/models.py:225 core/models.py:225 +msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only." +msgstr "Erforderlich. 255 Zeichen oder weniger. Buchstaben, Zahlen und die Zeichen @/./+/-/_/:" + +#: build/lib/core/models.py:234 core/models.py:234 +msgid "full name" +msgstr "Name" + +#: build/lib/core/models.py:235 core/models.py:235 +msgid "short name" +msgstr "Kurzbezeichnung" + +#: build/lib/core/models.py:237 core/models.py:237 +msgid "identity email address" +msgstr "Identitรคts-E-Mail-Adresse" + +#: build/lib/core/models.py:242 core/models.py:242 +msgid "admin email address" +msgstr "Admin E-Mail-Adresse" + +#: build/lib/core/models.py:249 core/models.py:249 +msgid "language" +msgstr "Sprache" + +#: build/lib/core/models.py:250 core/models.py:250 +msgid "The language in which the user wants to see the interface." +msgstr "Die Sprache, in der der Benutzer die Benutzeroberflรคche sehen mรถchte." + +#: build/lib/core/models.py:258 core/models.py:258 +msgid "The timezone in which the user wants to see times." +msgstr "Die Zeitzone, in der der Nutzer Zeiten sehen mรถchte." + +#: build/lib/core/models.py:261 core/models.py:261 +msgid "device" +msgstr "Gerรคt" + +#: build/lib/core/models.py:263 core/models.py:263 +msgid "Whether the user is a device or a real user." +msgstr "Ob der Benutzer ein Gerรคt oder ein echter Benutzer ist." + +#: build/lib/core/models.py:266 core/models.py:266 +msgid "staff status" +msgstr "Status des Teammitgliedes" + +#: build/lib/core/models.py:268 core/models.py:268 +msgid "Whether the user can log into this admin site." +msgstr "Gibt an, ob der Benutzer sich in diese Admin-Seite einloggen kann." + +#: build/lib/core/models.py:271 core/models.py:271 +msgid "active" +msgstr "aktiviert" + +#: build/lib/core/models.py:274 core/models.py:274 +msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts." +msgstr "Ob dieser Benutzer als aktiviert behandelt werden soll. Deaktivieren Sie diese Option, anstatt Konten zu lรถschen." + +#: build/lib/core/models.py:286 core/models.py:286 +msgid "user" +msgstr "Benutzer" + +#: build/lib/core/models.py:287 core/models.py:287 +msgid "users" +msgstr "Benutzer" + +#: build/lib/core/models.py:470 build/lib/core/models.py:1155 +#: core/models.py:470 core/models.py:1155 +msgid "title" +msgstr "Titel" + +#: build/lib/core/models.py:471 core/models.py:471 +msgid "excerpt" +msgstr "Auszug" + +#: build/lib/core/models.py:519 core/models.py:519 +msgid "Document" +msgstr "Dokument" + +#: build/lib/core/models.py:520 core/models.py:520 +msgid "Documents" +msgstr "Dokumente" + +#: build/lib/core/models.py:532 build/lib/core/models.py:873 core/models.py:532 +#: core/models.py:873 +msgid "Untitled Document" +msgstr "Unbenanntes Dokument" + +#: build/lib/core/models.py:908 core/models.py:908 +#, python-brace-format +msgid "{name} shared a document with you!" +msgstr "{name} hat ein Dokument mit Ihnen geteilt!" + +#: build/lib/core/models.py:912 core/models.py:912 +#, python-brace-format +msgid "{name} invited you with the role \"{role}\" on the following document:" +msgstr "{name} hat Sie mit der Rolle \"{role}\" zu folgendem Dokument eingeladen:" + +#: build/lib/core/models.py:918 core/models.py:918 +#, python-brace-format +msgid "{name} shared a document with you: {title}" +msgstr "{name} hat ein Dokument mit Ihnen geteilt: {title}" + +#: build/lib/core/models.py:1016 core/models.py:1016 +msgid "Document/user link trace" +msgstr "Dokument/Benutzer Linkverfolgung" + +#: build/lib/core/models.py:1017 core/models.py:1017 +msgid "Document/user link traces" +msgstr "Dokument/Benutzer Linkverfolgung" + +#: build/lib/core/models.py:1023 core/models.py:1023 +msgid "A link trace already exists for this document/user." +msgstr "Fรผr dieses Dokument/ diesen Benutzer ist bereits eine Linkverfolgung vorhanden." + +#: build/lib/core/models.py:1046 core/models.py:1046 +msgid "Document favorite" +msgstr "Dokumentenfavorit" + +#: build/lib/core/models.py:1047 core/models.py:1047 +msgid "Document favorites" +msgstr "Dokumentfavoriten" + +#: build/lib/core/models.py:1053 core/models.py:1053 +msgid "This document is already targeted by a favorite relation instance for the same user." +msgstr "Dieses Dokument ist bereits durch den gleichen Benutzer favorisiert worden." + +#: build/lib/core/models.py:1075 core/models.py:1075 +msgid "Document/user relation" +msgstr "Dokument/Benutzerbeziehung" + +#: build/lib/core/models.py:1076 core/models.py:1076 +msgid "Document/user relations" +msgstr "Dokument/Benutzerbeziehungen" + +#: build/lib/core/models.py:1082 core/models.py:1082 +msgid "This user is already in this document." +msgstr "Dieser Benutzer befindet sich bereits in diesem Dokument." + +#: build/lib/core/models.py:1088 core/models.py:1088 +msgid "This team is already in this document." +msgstr "Dieses Team befindet sich bereits in diesem Dokument." + +#: build/lib/core/models.py:1094 build/lib/core/models.py:1242 +#: core/models.py:1094 core/models.py:1242 +msgid "Either user or team must be set, not both." +msgstr "Benutzer oder Team mรผssen gesetzt werden, nicht beides." + +#: build/lib/core/models.py:1156 core/models.py:1156 +msgid "description" +msgstr "Beschreibung" + +#: build/lib/core/models.py:1157 core/models.py:1157 +msgid "code" +msgstr "Code" + +#: build/lib/core/models.py:1158 core/models.py:1158 +msgid "css" +msgstr "CSS" + +#: build/lib/core/models.py:1160 core/models.py:1160 +msgid "public" +msgstr "รถffentlich" + +#: build/lib/core/models.py:1162 core/models.py:1162 +msgid "Whether this template is public for anyone to use." +msgstr "Ob diese Vorlage fรผr jedermann รถffentlich ist." + +#: build/lib/core/models.py:1168 core/models.py:1168 +msgid "Template" +msgstr "Vorlage" + +#: build/lib/core/models.py:1169 core/models.py:1169 +msgid "Templates" +msgstr "Vorlagen" + +#: build/lib/core/models.py:1223 core/models.py:1223 +msgid "Template/user relation" +msgstr "Vorlage/Benutzer-Beziehung" + +#: build/lib/core/models.py:1224 core/models.py:1224 +msgid "Template/user relations" +msgstr "Vorlage/Benutzerbeziehungen" + +#: build/lib/core/models.py:1230 core/models.py:1230 +msgid "This user is already in this template." +msgstr "Dieser Benutzer ist bereits in dieser Vorlage." + +#: build/lib/core/models.py:1236 core/models.py:1236 +msgid "This team is already in this template." +msgstr "Dieses Team ist bereits in diesem Template." + +#: build/lib/core/models.py:1259 core/models.py:1259 +msgid "email address" +msgstr "E-Mail-Adresse" + +#: build/lib/core/models.py:1278 core/models.py:1278 +msgid "Document invitation" +msgstr "Einladung zum Dokument" + +#: build/lib/core/models.py:1279 core/models.py:1279 +msgid "Document invitations" +msgstr "Dokumenteinladungen" + +#: build/lib/core/models.py:1299 core/models.py:1299 +msgid "This email is already associated to a registered user." +msgstr "Diese E-Mail ist bereits einem registrierten Benutzer zugeordnet." + +#: core/templates/mail/html/invitation.html:162 +#: core/templates/mail/text/invitation.txt:3 +msgid "Logo email" +msgstr "Logo-E-Mail" + +#: core/templates/mail/html/invitation.html:209 +#: core/templates/mail/text/invitation.txt:10 +msgid "Open" +msgstr "ร–ffnen" + +#: core/templates/mail/html/invitation.html:226 +#: core/templates/mail/text/invitation.txt:14 +msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. " +msgstr " Docs, Ihr neues unentbehrliches Werkzeug fรผr die Organisation, den Austausch und die Zusammenarbeit in Ihren Dokumenten als Team. " + +#: core/templates/mail/html/invitation.html:233 +#: core/templates/mail/text/invitation.txt:16 +#, python-format +msgid " Brought to you by %(brandname)s " +msgstr " Erstellt von %(brandname)s " + diff --git a/src/backend/locale/en_US/LC_MESSAGES/django.po b/src/backend/locale/en_US/LC_MESSAGES/django.po new file mode 100644 index 0000000..ae5faff --- /dev/null +++ b/src/backend/locale/en_US/LC_MESSAGES/django.po @@ -0,0 +1,390 @@ +msgid "" +msgstr "" +"Project-Id-Version: lasuite-docs\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-05-22 12:09+0000\n" +"PO-Revision-Date: 2025-05-22 14:16\n" +"Last-Translator: \n" +"Language-Team: English\n" +"Language: en_US\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Crowdin-Project: lasuite-docs\n" +"X-Crowdin-Project-ID: 754523\n" +"X-Crowdin-Language: en\n" +"X-Crowdin-File: backend-conversations.pot\n" +"X-Crowdin-File-ID: 18\n" + +#: build/lib/core/admin.py:37 core/admin.py:37 +msgid "Personal info" +msgstr "" + +#: build/lib/core/admin.py:50 build/lib/core/admin.py:138 core/admin.py:50 +#: core/admin.py:138 +msgid "Permissions" +msgstr "" + +#: build/lib/core/admin.py:62 core/admin.py:62 +msgid "Important dates" +msgstr "" + +#: build/lib/core/admin.py:148 core/admin.py:148 +msgid "Tree structure" +msgstr "" + +#: build/lib/core/api/filters.py:47 core/api/filters.py:47 +msgid "Title" +msgstr "" + +#: build/lib/core/api/filters.py:61 core/api/filters.py:61 +msgid "Creator is me" +msgstr "" + +#: build/lib/core/api/filters.py:64 core/api/filters.py:64 +msgid "Favorite" +msgstr "" + +#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446 +msgid "A new document was created on your behalf!" +msgstr "" + +#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450 +msgid "You have been granted ownership of a new document:" +msgstr "" + +#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586 +msgid "Body" +msgstr "" + +#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589 +msgid "Body type" +msgstr "" + +#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595 +msgid "Format" +msgstr "" + +#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967 +#, python-brace-format +msgid "copy of {title}" +msgstr "" + +#: build/lib/core/enums.py:36 core/enums.py:36 +msgid "First child" +msgstr "" + +#: build/lib/core/enums.py:37 core/enums.py:37 +msgid "Last child" +msgstr "" + +#: build/lib/core/enums.py:38 core/enums.py:38 +msgid "First sibling" +msgstr "" + +#: build/lib/core/enums.py:39 core/enums.py:39 +msgid "Last sibling" +msgstr "" + +#: build/lib/core/enums.py:40 core/enums.py:40 +msgid "Left" +msgstr "" + +#: build/lib/core/enums.py:41 core/enums.py:41 +msgid "Right" +msgstr "" + +#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56 +#: core/models.py:63 +msgid "Reader" +msgstr "" + +#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57 +#: core/models.py:64 +msgid "Editor" +msgstr "" + +#: build/lib/core/models.py:65 core/models.py:65 +msgid "Administrator" +msgstr "" + +#: build/lib/core/models.py:66 core/models.py:66 +msgid "Owner" +msgstr "" + +#: build/lib/core/models.py:77 core/models.py:77 +msgid "Restricted" +msgstr "" + +#: build/lib/core/models.py:81 core/models.py:81 +msgid "Authenticated" +msgstr "" + +#: build/lib/core/models.py:83 core/models.py:83 +msgid "Public" +msgstr "" + +#: build/lib/core/models.py:154 core/models.py:154 +msgid "id" +msgstr "" + +#: build/lib/core/models.py:155 core/models.py:155 +msgid "primary key for the record as UUID" +msgstr "" + +#: build/lib/core/models.py:161 core/models.py:161 +msgid "created on" +msgstr "" + +#: build/lib/core/models.py:162 core/models.py:162 +msgid "date and time at which a record was created" +msgstr "" + +#: build/lib/core/models.py:167 core/models.py:167 +msgid "updated on" +msgstr "" + +#: build/lib/core/models.py:168 core/models.py:168 +msgid "date and time at which a record was last updated" +msgstr "" + +#: build/lib/core/models.py:204 core/models.py:204 +msgid "We couldn't find a user with this sub but the email is already associated with a registered user." +msgstr "" + +#: build/lib/core/models.py:217 core/models.py:217 +msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters." +msgstr "" + +#: build/lib/core/models.py:223 core/models.py:223 +msgid "sub" +msgstr "" + +#: build/lib/core/models.py:225 core/models.py:225 +msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only." +msgstr "" + +#: build/lib/core/models.py:234 core/models.py:234 +msgid "full name" +msgstr "" + +#: build/lib/core/models.py:235 core/models.py:235 +msgid "short name" +msgstr "" + +#: build/lib/core/models.py:237 core/models.py:237 +msgid "identity email address" +msgstr "" + +#: build/lib/core/models.py:242 core/models.py:242 +msgid "admin email address" +msgstr "" + +#: build/lib/core/models.py:249 core/models.py:249 +msgid "language" +msgstr "" + +#: build/lib/core/models.py:250 core/models.py:250 +msgid "The language in which the user wants to see the interface." +msgstr "" + +#: build/lib/core/models.py:258 core/models.py:258 +msgid "The timezone in which the user wants to see times." +msgstr "" + +#: build/lib/core/models.py:261 core/models.py:261 +msgid "device" +msgstr "" + +#: build/lib/core/models.py:263 core/models.py:263 +msgid "Whether the user is a device or a real user." +msgstr "" + +#: build/lib/core/models.py:266 core/models.py:266 +msgid "staff status" +msgstr "" + +#: build/lib/core/models.py:268 core/models.py:268 +msgid "Whether the user can log into this admin site." +msgstr "" + +#: build/lib/core/models.py:271 core/models.py:271 +msgid "active" +msgstr "" + +#: build/lib/core/models.py:274 core/models.py:274 +msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts." +msgstr "" + +#: build/lib/core/models.py:286 core/models.py:286 +msgid "user" +msgstr "" + +#: build/lib/core/models.py:287 core/models.py:287 +msgid "users" +msgstr "" + +#: build/lib/core/models.py:470 build/lib/core/models.py:1155 +#: core/models.py:470 core/models.py:1155 +msgid "title" +msgstr "" + +#: build/lib/core/models.py:471 core/models.py:471 +msgid "excerpt" +msgstr "" + +#: build/lib/core/models.py:519 core/models.py:519 +msgid "Document" +msgstr "" + +#: build/lib/core/models.py:520 core/models.py:520 +msgid "Documents" +msgstr "" + +#: build/lib/core/models.py:532 build/lib/core/models.py:873 core/models.py:532 +#: core/models.py:873 +msgid "Untitled Document" +msgstr "" + +#: build/lib/core/models.py:908 core/models.py:908 +#, python-brace-format +msgid "{name} shared a document with you!" +msgstr "" + +#: build/lib/core/models.py:912 core/models.py:912 +#, python-brace-format +msgid "{name} invited you with the role \"{role}\" on the following document:" +msgstr "" + +#: build/lib/core/models.py:918 core/models.py:918 +#, python-brace-format +msgid "{name} shared a document with you: {title}" +msgstr "" + +#: build/lib/core/models.py:1016 core/models.py:1016 +msgid "Document/user link trace" +msgstr "" + +#: build/lib/core/models.py:1017 core/models.py:1017 +msgid "Document/user link traces" +msgstr "" + +#: build/lib/core/models.py:1023 core/models.py:1023 +msgid "A link trace already exists for this document/user." +msgstr "" + +#: build/lib/core/models.py:1046 core/models.py:1046 +msgid "Document favorite" +msgstr "" + +#: build/lib/core/models.py:1047 core/models.py:1047 +msgid "Document favorites" +msgstr "" + +#: build/lib/core/models.py:1053 core/models.py:1053 +msgid "This document is already targeted by a favorite relation instance for the same user." +msgstr "" + +#: build/lib/core/models.py:1075 core/models.py:1075 +msgid "Document/user relation" +msgstr "" + +#: build/lib/core/models.py:1076 core/models.py:1076 +msgid "Document/user relations" +msgstr "" + +#: build/lib/core/models.py:1082 core/models.py:1082 +msgid "This user is already in this document." +msgstr "" + +#: build/lib/core/models.py:1088 core/models.py:1088 +msgid "This team is already in this document." +msgstr "" + +#: build/lib/core/models.py:1094 build/lib/core/models.py:1242 +#: core/models.py:1094 core/models.py:1242 +msgid "Either user or team must be set, not both." +msgstr "" + +#: build/lib/core/models.py:1156 core/models.py:1156 +msgid "description" +msgstr "" + +#: build/lib/core/models.py:1157 core/models.py:1157 +msgid "code" +msgstr "" + +#: build/lib/core/models.py:1158 core/models.py:1158 +msgid "css" +msgstr "" + +#: build/lib/core/models.py:1160 core/models.py:1160 +msgid "public" +msgstr "" + +#: build/lib/core/models.py:1162 core/models.py:1162 +msgid "Whether this template is public for anyone to use." +msgstr "" + +#: build/lib/core/models.py:1168 core/models.py:1168 +msgid "Template" +msgstr "" + +#: build/lib/core/models.py:1169 core/models.py:1169 +msgid "Templates" +msgstr "" + +#: build/lib/core/models.py:1223 core/models.py:1223 +msgid "Template/user relation" +msgstr "" + +#: build/lib/core/models.py:1224 core/models.py:1224 +msgid "Template/user relations" +msgstr "" + +#: build/lib/core/models.py:1230 core/models.py:1230 +msgid "This user is already in this template." +msgstr "" + +#: build/lib/core/models.py:1236 core/models.py:1236 +msgid "This team is already in this template." +msgstr "" + +#: build/lib/core/models.py:1259 core/models.py:1259 +msgid "email address" +msgstr "" + +#: build/lib/core/models.py:1278 core/models.py:1278 +msgid "Document invitation" +msgstr "" + +#: build/lib/core/models.py:1279 core/models.py:1279 +msgid "Document invitations" +msgstr "" + +#: build/lib/core/models.py:1299 core/models.py:1299 +msgid "This email is already associated to a registered user." +msgstr "" + +#: core/templates/mail/html/invitation.html:162 +#: core/templates/mail/text/invitation.txt:3 +msgid "Logo email" +msgstr "" + +#: core/templates/mail/html/invitation.html:209 +#: core/templates/mail/text/invitation.txt:10 +msgid "Open" +msgstr "" + +#: core/templates/mail/html/invitation.html:226 +#: core/templates/mail/text/invitation.txt:14 +msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. " +msgstr "" + +#: core/templates/mail/html/invitation.html:233 +#: core/templates/mail/text/invitation.txt:16 +#, python-format +msgid " Brought to you by %(brandname)s " +msgstr "" + diff --git a/src/backend/locale/es_ES/LC_MESSAGES/django.po b/src/backend/locale/es_ES/LC_MESSAGES/django.po new file mode 100644 index 0000000..192b9d2 --- /dev/null +++ b/src/backend/locale/es_ES/LC_MESSAGES/django.po @@ -0,0 +1,390 @@ +msgid "" +msgstr "" +"Project-Id-Version: lasuite-docs\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-05-22 12:09+0000\n" +"PO-Revision-Date: 2025-05-22 14:16\n" +"Last-Translator: \n" +"Language-Team: Spanish\n" +"Language: es_ES\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Crowdin-Project: lasuite-docs\n" +"X-Crowdin-Project-ID: 754523\n" +"X-Crowdin-Language: es-ES\n" +"X-Crowdin-File: backend-conversations.pot\n" +"X-Crowdin-File-ID: 18\n" + +#: build/lib/core/admin.py:37 core/admin.py:37 +msgid "Personal info" +msgstr "Informaciรณn Personal" + +#: build/lib/core/admin.py:50 build/lib/core/admin.py:138 core/admin.py:50 +#: core/admin.py:138 +msgid "Permissions" +msgstr "Permisos" + +#: build/lib/core/admin.py:62 core/admin.py:62 +msgid "Important dates" +msgstr "Fechas importantes" + +#: build/lib/core/admin.py:148 core/admin.py:148 +msgid "Tree structure" +msgstr "Estructura en รกrbol" + +#: build/lib/core/api/filters.py:47 core/api/filters.py:47 +msgid "Title" +msgstr "Tรญtulo" + +#: build/lib/core/api/filters.py:61 core/api/filters.py:61 +msgid "Creator is me" +msgstr "Yo soy el creador" + +#: build/lib/core/api/filters.py:64 core/api/filters.py:64 +msgid "Favorite" +msgstr "Favorito" + +#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446 +msgid "A new document was created on your behalf!" +msgstr "ยกUn nuevo documento se ha creado por ti!" + +#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450 +msgid "You have been granted ownership of a new document:" +msgstr "Se le ha concedido la propiedad de un nuevo documento :" + +#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586 +msgid "Body" +msgstr "Cuerpo" + +#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589 +msgid "Body type" +msgstr "Tipo de Cuerpo" + +#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595 +msgid "Format" +msgstr "Formato" + +#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967 +#, python-brace-format +msgid "copy of {title}" +msgstr "copia de {title}" + +#: build/lib/core/enums.py:36 core/enums.py:36 +msgid "First child" +msgstr "Primer nodo" + +#: build/lib/core/enums.py:37 core/enums.py:37 +msgid "Last child" +msgstr "รšltimo nodo" + +#: build/lib/core/enums.py:38 core/enums.py:38 +msgid "First sibling" +msgstr "Primera relaciรณn" + +#: build/lib/core/enums.py:39 core/enums.py:39 +msgid "Last sibling" +msgstr "รšltima relaciรณn" + +#: build/lib/core/enums.py:40 core/enums.py:40 +msgid "Left" +msgstr "Izquierda" + +#: build/lib/core/enums.py:41 core/enums.py:41 +msgid "Right" +msgstr "Derecha" + +#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56 +#: core/models.py:63 +msgid "Reader" +msgstr "Lector" + +#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57 +#: core/models.py:64 +msgid "Editor" +msgstr "Editor" + +#: build/lib/core/models.py:65 core/models.py:65 +msgid "Administrator" +msgstr "Administrador" + +#: build/lib/core/models.py:66 core/models.py:66 +msgid "Owner" +msgstr "Propietario" + +#: build/lib/core/models.py:77 core/models.py:77 +msgid "Restricted" +msgstr "Restringido" + +#: build/lib/core/models.py:81 core/models.py:81 +msgid "Authenticated" +msgstr "Autentificado" + +#: build/lib/core/models.py:83 core/models.py:83 +msgid "Public" +msgstr "Pรบblico" + +#: build/lib/core/models.py:154 core/models.py:154 +msgid "id" +msgstr "id" + +#: build/lib/core/models.py:155 core/models.py:155 +msgid "primary key for the record as UUID" +msgstr "clave primaria para el registro como UUID" + +#: build/lib/core/models.py:161 core/models.py:161 +msgid "created on" +msgstr "creado el" + +#: build/lib/core/models.py:162 core/models.py:162 +msgid "date and time at which a record was created" +msgstr "fecha y hora en la que se creรณ un registro" + +#: build/lib/core/models.py:167 core/models.py:167 +msgid "updated on" +msgstr "actualizado el" + +#: build/lib/core/models.py:168 core/models.py:168 +msgid "date and time at which a record was last updated" +msgstr "fecha y hora en la que un registro fue actualizado por รบltima vez" + +#: build/lib/core/models.py:204 core/models.py:204 +msgid "We couldn't find a user with this sub but the email is already associated with a registered user." +msgstr "No se ha podido encontrar un usuario con este sub (UUID), pero el correo electrรณnico ya estรก asociado con un usuario." + +#: build/lib/core/models.py:217 core/models.py:217 +msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters." +msgstr "Introduzca un sub (UUID) vรกlido. Este valor solo puede contener letras, nรบmeros y los siguientes caracteres @/./+/-/_/:" + +#: build/lib/core/models.py:223 core/models.py:223 +msgid "sub" +msgstr "sub (UUID)" + +#: build/lib/core/models.py:225 core/models.py:225 +msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only." +msgstr "Requerido. 255 caracteres o menos. Letras, nรบmeros y los siguientes caracteres @/./+/-/_/: solamente." + +#: build/lib/core/models.py:234 core/models.py:234 +msgid "full name" +msgstr "nombre completo" + +#: build/lib/core/models.py:235 core/models.py:235 +msgid "short name" +msgstr "nombre abreviado" + +#: build/lib/core/models.py:237 core/models.py:237 +msgid "identity email address" +msgstr "correo electrรณnico de identidad" + +#: build/lib/core/models.py:242 core/models.py:242 +msgid "admin email address" +msgstr "correo electrรณnico del administrador" + +#: build/lib/core/models.py:249 core/models.py:249 +msgid "language" +msgstr "idioma" + +#: build/lib/core/models.py:250 core/models.py:250 +msgid "The language in which the user wants to see the interface." +msgstr "El idioma en el que el usuario desea ver la interfaz." + +#: build/lib/core/models.py:258 core/models.py:258 +msgid "The timezone in which the user wants to see times." +msgstr "La zona horaria en la que el usuario quiere ver los tiempos." + +#: build/lib/core/models.py:261 core/models.py:261 +msgid "device" +msgstr "dispositivo" + +#: build/lib/core/models.py:263 core/models.py:263 +msgid "Whether the user is a device or a real user." +msgstr "Si el usuario es un dispositivo o un usuario real." + +#: build/lib/core/models.py:266 core/models.py:266 +msgid "staff status" +msgstr "rol en el equipo" + +#: build/lib/core/models.py:268 core/models.py:268 +msgid "Whether the user can log into this admin site." +msgstr "Si el usuario puede iniciar sesiรณn en esta pรกgina web de administraciรณn." + +#: build/lib/core/models.py:271 core/models.py:271 +msgid "active" +msgstr "activo" + +#: build/lib/core/models.py:274 core/models.py:274 +msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts." +msgstr "Si este usuario debe ser considerado como activo. Deseleccionar en lugar de eliminar cuentas." + +#: build/lib/core/models.py:286 core/models.py:286 +msgid "user" +msgstr "usuario" + +#: build/lib/core/models.py:287 core/models.py:287 +msgid "users" +msgstr "usuarios" + +#: build/lib/core/models.py:470 build/lib/core/models.py:1155 +#: core/models.py:470 core/models.py:1155 +msgid "title" +msgstr "tรญtulo" + +#: build/lib/core/models.py:471 core/models.py:471 +msgid "excerpt" +msgstr "resumen" + +#: build/lib/core/models.py:519 core/models.py:519 +msgid "Document" +msgstr "Documento" + +#: build/lib/core/models.py:520 core/models.py:520 +msgid "Documents" +msgstr "Documentos" + +#: build/lib/core/models.py:532 build/lib/core/models.py:873 core/models.py:532 +#: core/models.py:873 +msgid "Untitled Document" +msgstr "Documento sin tรญtulo" + +#: build/lib/core/models.py:908 core/models.py:908 +#, python-brace-format +msgid "{name} shared a document with you!" +msgstr "ยก{name} ha compartido un documento contigo!" + +#: build/lib/core/models.py:912 core/models.py:912 +#, python-brace-format +msgid "{name} invited you with the role \"{role}\" on the following document:" +msgstr "Te ha invitado {name} al siguiente documento con el rol \"{role}\" :" + +#: build/lib/core/models.py:918 core/models.py:918 +#, python-brace-format +msgid "{name} shared a document with you: {title}" +msgstr "{name} ha compartido un documento contigo: {title}" + +#: build/lib/core/models.py:1016 core/models.py:1016 +msgid "Document/user link trace" +msgstr "Traza del enlace de documento/usuario" + +#: build/lib/core/models.py:1017 core/models.py:1017 +msgid "Document/user link traces" +msgstr "Trazas del enlace de documento/usuario" + +#: build/lib/core/models.py:1023 core/models.py:1023 +msgid "A link trace already exists for this document/user." +msgstr "Ya existe una traza de enlace para este documento/usuario." + +#: build/lib/core/models.py:1046 core/models.py:1046 +msgid "Document favorite" +msgstr "Documento favorito" + +#: build/lib/core/models.py:1047 core/models.py:1047 +msgid "Document favorites" +msgstr "Documentos favoritos" + +#: build/lib/core/models.py:1053 core/models.py:1053 +msgid "This document is already targeted by a favorite relation instance for the same user." +msgstr "Este documento ya ha sido marcado como favorito por el usuario." + +#: build/lib/core/models.py:1075 core/models.py:1075 +msgid "Document/user relation" +msgstr "Relaciรณn documento/usuario" + +#: build/lib/core/models.py:1076 core/models.py:1076 +msgid "Document/user relations" +msgstr "Relaciones documento/usuario" + +#: build/lib/core/models.py:1082 core/models.py:1082 +msgid "This user is already in this document." +msgstr "Este usuario ya forma parte del documento." + +#: build/lib/core/models.py:1088 core/models.py:1088 +msgid "This team is already in this document." +msgstr "Este equipo ya forma parte del documento." + +#: build/lib/core/models.py:1094 build/lib/core/models.py:1242 +#: core/models.py:1094 core/models.py:1242 +msgid "Either user or team must be set, not both." +msgstr "Debe establecerse un usuario o un equipo, no ambos." + +#: build/lib/core/models.py:1156 core/models.py:1156 +msgid "description" +msgstr "descripciรณn" + +#: build/lib/core/models.py:1157 core/models.py:1157 +msgid "code" +msgstr "cรณdigo" + +#: build/lib/core/models.py:1158 core/models.py:1158 +msgid "css" +msgstr "css" + +#: build/lib/core/models.py:1160 core/models.py:1160 +msgid "public" +msgstr "pรบblico" + +#: build/lib/core/models.py:1162 core/models.py:1162 +msgid "Whether this template is public for anyone to use." +msgstr "Si esta plantilla es pรบblica para que cualquiera la utilice." + +#: build/lib/core/models.py:1168 core/models.py:1168 +msgid "Template" +msgstr "Plantilla" + +#: build/lib/core/models.py:1169 core/models.py:1169 +msgid "Templates" +msgstr "Plantillas" + +#: build/lib/core/models.py:1223 core/models.py:1223 +msgid "Template/user relation" +msgstr "Relaciรณn plantilla/usuario" + +#: build/lib/core/models.py:1224 core/models.py:1224 +msgid "Template/user relations" +msgstr "Relaciones plantilla/usuario" + +#: build/lib/core/models.py:1230 core/models.py:1230 +msgid "This user is already in this template." +msgstr "Este usuario ya forma parte de la plantilla." + +#: build/lib/core/models.py:1236 core/models.py:1236 +msgid "This team is already in this template." +msgstr "Este equipo ya se encuentra en esta plantilla." + +#: build/lib/core/models.py:1259 core/models.py:1259 +msgid "email address" +msgstr "direcciรณn de correo electrรณnico" + +#: build/lib/core/models.py:1278 core/models.py:1278 +msgid "Document invitation" +msgstr "Invitaciรณn al documento" + +#: build/lib/core/models.py:1279 core/models.py:1279 +msgid "Document invitations" +msgstr "Invitaciones a documentos" + +#: build/lib/core/models.py:1299 core/models.py:1299 +msgid "This email is already associated to a registered user." +msgstr "Este correo electrรณnico estรก asociado a un usuario registrado." + +#: core/templates/mail/html/invitation.html:162 +#: core/templates/mail/text/invitation.txt:3 +msgid "Logo email" +msgstr "Logo de correo electrรณnico" + +#: core/templates/mail/html/invitation.html:209 +#: core/templates/mail/text/invitation.txt:10 +msgid "Open" +msgstr "Abrir" + +#: core/templates/mail/html/invitation.html:226 +#: core/templates/mail/text/invitation.txt:14 +msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. " +msgstr "Docs, su nueva herramienta esencial para organizar, compartir y colaborar en sus documentos como equipo." + +#: core/templates/mail/html/invitation.html:233 +#: core/templates/mail/text/invitation.txt:16 +#, python-format +msgid " Brought to you by %(brandname)s " +msgstr " Presentado por %(brandname)s " + diff --git a/src/backend/locale/fr_FR/LC_MESSAGES/django.po b/src/backend/locale/fr_FR/LC_MESSAGES/django.po new file mode 100644 index 0000000..a7b7374 --- /dev/null +++ b/src/backend/locale/fr_FR/LC_MESSAGES/django.po @@ -0,0 +1,390 @@ +msgid "" +msgstr "" +"Project-Id-Version: lasuite-docs\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-05-22 12:09+0000\n" +"PO-Revision-Date: 2025-05-22 14:16\n" +"Last-Translator: \n" +"Language-Team: French\n" +"Language: fr_FR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Crowdin-Project: lasuite-docs\n" +"X-Crowdin-Project-ID: 754523\n" +"X-Crowdin-Language: fr\n" +"X-Crowdin-File: backend-conversations.pot\n" +"X-Crowdin-File-ID: 18\n" + +#: build/lib/core/admin.py:37 core/admin.py:37 +msgid "Personal info" +msgstr "Infos Personnelles" + +#: build/lib/core/admin.py:50 build/lib/core/admin.py:138 core/admin.py:50 +#: core/admin.py:138 +msgid "Permissions" +msgstr "Permissions" + +#: build/lib/core/admin.py:62 core/admin.py:62 +msgid "Important dates" +msgstr "Dates importantes" + +#: build/lib/core/admin.py:148 core/admin.py:148 +msgid "Tree structure" +msgstr "Arborescence" + +#: build/lib/core/api/filters.py:47 core/api/filters.py:47 +msgid "Title" +msgstr "Titre" + +#: build/lib/core/api/filters.py:61 core/api/filters.py:61 +msgid "Creator is me" +msgstr "Je suis l'auteur" + +#: build/lib/core/api/filters.py:64 core/api/filters.py:64 +msgid "Favorite" +msgstr "Favoris" + +#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446 +msgid "A new document was created on your behalf!" +msgstr "Un nouveau document a รฉtรฉ crรฉรฉ pour vous !" + +#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450 +msgid "You have been granted ownership of a new document:" +msgstr "Vous avez รฉtรฉ dรฉclarรฉ propriรฉtaire d'un nouveau document :" + +#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586 +msgid "Body" +msgstr "Corps" + +#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589 +msgid "Body type" +msgstr "Type de corps" + +#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595 +msgid "Format" +msgstr "Format" + +#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967 +#, python-brace-format +msgid "copy of {title}" +msgstr "copie de {title}" + +#: build/lib/core/enums.py:36 core/enums.py:36 +msgid "First child" +msgstr "Premier enfant" + +#: build/lib/core/enums.py:37 core/enums.py:37 +msgid "Last child" +msgstr "Dernier enfant" + +#: build/lib/core/enums.py:38 core/enums.py:38 +msgid "First sibling" +msgstr "Premier frรจre ou sล“ur" + +#: build/lib/core/enums.py:39 core/enums.py:39 +msgid "Last sibling" +msgstr "Derniรจre relation" + +#: build/lib/core/enums.py:40 core/enums.py:40 +msgid "Left" +msgstr "Gauche" + +#: build/lib/core/enums.py:41 core/enums.py:41 +msgid "Right" +msgstr "Droite" + +#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56 +#: core/models.py:63 +msgid "Reader" +msgstr "Lecteur" + +#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57 +#: core/models.py:64 +msgid "Editor" +msgstr "ร‰diteur" + +#: build/lib/core/models.py:65 core/models.py:65 +msgid "Administrator" +msgstr "Administrateur" + +#: build/lib/core/models.py:66 core/models.py:66 +msgid "Owner" +msgstr "Propriรฉtaire" + +#: build/lib/core/models.py:77 core/models.py:77 +msgid "Restricted" +msgstr "Restreint" + +#: build/lib/core/models.py:81 core/models.py:81 +msgid "Authenticated" +msgstr "Authentifiรฉ" + +#: build/lib/core/models.py:83 core/models.py:83 +msgid "Public" +msgstr "Public" + +#: build/lib/core/models.py:154 core/models.py:154 +msgid "id" +msgstr "identifiant/id" + +#: build/lib/core/models.py:155 core/models.py:155 +msgid "primary key for the record as UUID" +msgstr "clรฉ primaire pour l'enregistrement en tant que UUID" + +#: build/lib/core/models.py:161 core/models.py:161 +msgid "created on" +msgstr "crรฉรฉ le" + +#: build/lib/core/models.py:162 core/models.py:162 +msgid "date and time at which a record was created" +msgstr "date et heure de crรฉation de l'enregistrement" + +#: build/lib/core/models.py:167 core/models.py:167 +msgid "updated on" +msgstr "mis ร  jour le" + +#: build/lib/core/models.py:168 core/models.py:168 +msgid "date and time at which a record was last updated" +msgstr "date et heure de la derniรจre mise ร  jour de l'enregistrement" + +#: build/lib/core/models.py:204 core/models.py:204 +msgid "We couldn't find a user with this sub but the email is already associated with a registered user." +msgstr "Nous n'avons pas pu trouver un utilisateur avec ce sous-groupe mais l'e-mail est dรฉjร  associรฉ ร  un utilisateur enregistrรฉ." + +#: build/lib/core/models.py:217 core/models.py:217 +msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters." +msgstr "Saisissez un sous-groupe valide. Cette valeur ne peut contenir que des lettres, des chiffres et les caractรจres @/./+/-/_/: uniquement." + +#: build/lib/core/models.py:223 core/models.py:223 +msgid "sub" +msgstr "sous-groupe" + +#: build/lib/core/models.py:225 core/models.py:225 +msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only." +msgstr "Obligatoire. 255 caractรจres ou moins. Lettres, chiffres et caractรจres @/./+/-/_/: uniquement." + +#: build/lib/core/models.py:234 core/models.py:234 +msgid "full name" +msgstr "nom complet" + +#: build/lib/core/models.py:235 core/models.py:235 +msgid "short name" +msgstr "nom court" + +#: build/lib/core/models.py:237 core/models.py:237 +msgid "identity email address" +msgstr "adresse e-mail d'identitรฉ" + +#: build/lib/core/models.py:242 core/models.py:242 +msgid "admin email address" +msgstr "adresse e-mail de l'administrateur" + +#: build/lib/core/models.py:249 core/models.py:249 +msgid "language" +msgstr "langue" + +#: build/lib/core/models.py:250 core/models.py:250 +msgid "The language in which the user wants to see the interface." +msgstr "La langue dans laquelle l'utilisateur veut voir l'interface." + +#: build/lib/core/models.py:258 core/models.py:258 +msgid "The timezone in which the user wants to see times." +msgstr "Le fuseau horaire dans lequel l'utilisateur souhaite voir les heures." + +#: build/lib/core/models.py:261 core/models.py:261 +msgid "device" +msgstr "appareil" + +#: build/lib/core/models.py:263 core/models.py:263 +msgid "Whether the user is a device or a real user." +msgstr "Si l'utilisateur est un appareil ou un utilisateur rรฉel." + +#: build/lib/core/models.py:266 core/models.py:266 +msgid "staff status" +msgstr "statut d'รฉquipe" + +#: build/lib/core/models.py:268 core/models.py:268 +msgid "Whether the user can log into this admin site." +msgstr "Si l'utilisateur peut se connecter ร  ce site d'administration." + +#: build/lib/core/models.py:271 core/models.py:271 +msgid "active" +msgstr "actif" + +#: build/lib/core/models.py:274 core/models.py:274 +msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts." +msgstr "Si cet utilisateur doit รชtre traitรฉ comme actif. Dรฉsรฉlectionnez ceci au lieu de supprimer des comptes." + +#: build/lib/core/models.py:286 core/models.py:286 +msgid "user" +msgstr "utilisateur" + +#: build/lib/core/models.py:287 core/models.py:287 +msgid "users" +msgstr "utilisateurs" + +#: build/lib/core/models.py:470 build/lib/core/models.py:1155 +#: core/models.py:470 core/models.py:1155 +msgid "title" +msgstr "titre" + +#: build/lib/core/models.py:471 core/models.py:471 +msgid "excerpt" +msgstr "extrait" + +#: build/lib/core/models.py:519 core/models.py:519 +msgid "Document" +msgstr "Document" + +#: build/lib/core/models.py:520 core/models.py:520 +msgid "Documents" +msgstr "Documents" + +#: build/lib/core/models.py:532 build/lib/core/models.py:873 core/models.py:532 +#: core/models.py:873 +msgid "Untitled Document" +msgstr "Document sans titre" + +#: build/lib/core/models.py:908 core/models.py:908 +#, python-brace-format +msgid "{name} shared a document with you!" +msgstr "{name} a partagรฉ un document avec vous!" + +#: build/lib/core/models.py:912 core/models.py:912 +#, python-brace-format +msgid "{name} invited you with the role \"{role}\" on the following document:" +msgstr "{name} vous a invitรฉ avec le rรดle \"{role}\" sur le document suivant :" + +#: build/lib/core/models.py:918 core/models.py:918 +#, python-brace-format +msgid "{name} shared a document with you: {title}" +msgstr "{name} a partagรฉ un document avec vous : {title}" + +#: build/lib/core/models.py:1016 core/models.py:1016 +msgid "Document/user link trace" +msgstr "Trace du lien document/utilisateur" + +#: build/lib/core/models.py:1017 core/models.py:1017 +msgid "Document/user link traces" +msgstr "Traces du lien document/utilisateur" + +#: build/lib/core/models.py:1023 core/models.py:1023 +msgid "A link trace already exists for this document/user." +msgstr "Une trace de lien existe dรฉjร  pour ce document/utilisateur." + +#: build/lib/core/models.py:1046 core/models.py:1046 +msgid "Document favorite" +msgstr "Document favori" + +#: build/lib/core/models.py:1047 core/models.py:1047 +msgid "Document favorites" +msgstr "Documents favoris" + +#: build/lib/core/models.py:1053 core/models.py:1053 +msgid "This document is already targeted by a favorite relation instance for the same user." +msgstr "Ce document est dรฉjร  un favori de cet utilisateur." + +#: build/lib/core/models.py:1075 core/models.py:1075 +msgid "Document/user relation" +msgstr "Relation document/utilisateur" + +#: build/lib/core/models.py:1076 core/models.py:1076 +msgid "Document/user relations" +msgstr "Relations document/utilisateur" + +#: build/lib/core/models.py:1082 core/models.py:1082 +msgid "This user is already in this document." +msgstr "Cet utilisateur est dรฉjร  dans ce document." + +#: build/lib/core/models.py:1088 core/models.py:1088 +msgid "This team is already in this document." +msgstr "Cette รฉquipe est dรฉjร  dans ce document." + +#: build/lib/core/models.py:1094 build/lib/core/models.py:1242 +#: core/models.py:1094 core/models.py:1242 +msgid "Either user or team must be set, not both." +msgstr "L'utilisateur ou l'รฉquipe doivent รชtre dรฉfinis, pas les deux." + +#: build/lib/core/models.py:1156 core/models.py:1156 +msgid "description" +msgstr "description" + +#: build/lib/core/models.py:1157 core/models.py:1157 +msgid "code" +msgstr "code" + +#: build/lib/core/models.py:1158 core/models.py:1158 +msgid "css" +msgstr "CSS" + +#: build/lib/core/models.py:1160 core/models.py:1160 +msgid "public" +msgstr "public" + +#: build/lib/core/models.py:1162 core/models.py:1162 +msgid "Whether this template is public for anyone to use." +msgstr "Si ce modรจle est public, utilisable par n'importe qui." + +#: build/lib/core/models.py:1168 core/models.py:1168 +msgid "Template" +msgstr "Modรจle" + +#: build/lib/core/models.py:1169 core/models.py:1169 +msgid "Templates" +msgstr "Modรจles" + +#: build/lib/core/models.py:1223 core/models.py:1223 +msgid "Template/user relation" +msgstr "Relation modรจle/utilisateur" + +#: build/lib/core/models.py:1224 core/models.py:1224 +msgid "Template/user relations" +msgstr "Relations modรจle/utilisateur" + +#: build/lib/core/models.py:1230 core/models.py:1230 +msgid "This user is already in this template." +msgstr "Cet utilisateur est dรฉjร  dans ce modรจle." + +#: build/lib/core/models.py:1236 core/models.py:1236 +msgid "This team is already in this template." +msgstr "Cette รฉquipe est dรฉjร  modรจle." + +#: build/lib/core/models.py:1259 core/models.py:1259 +msgid "email address" +msgstr "adresse e-mail" + +#: build/lib/core/models.py:1278 core/models.py:1278 +msgid "Document invitation" +msgstr "Invitation ร  un document" + +#: build/lib/core/models.py:1279 core/models.py:1279 +msgid "Document invitations" +msgstr "Invitations ร  un document" + +#: build/lib/core/models.py:1299 core/models.py:1299 +msgid "This email is already associated to a registered user." +msgstr "Cette adresse email est dรฉjร  associรฉe ร  un utilisateur inscrit." + +#: core/templates/mail/html/invitation.html:162 +#: core/templates/mail/text/invitation.txt:3 +msgid "Logo email" +msgstr "Logo de l'e-mail" + +#: core/templates/mail/html/invitation.html:209 +#: core/templates/mail/text/invitation.txt:10 +msgid "Open" +msgstr "Ouvrir" + +#: core/templates/mail/html/invitation.html:226 +#: core/templates/mail/text/invitation.txt:14 +msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. " +msgstr " Docs, votre nouvel outil incontournable pour organiser, partager et collaborer sur vos documents en รฉquipe. " + +#: core/templates/mail/html/invitation.html:233 +#: core/templates/mail/text/invitation.txt:16 +#, python-format +msgid " Brought to you by %(brandname)s " +msgstr " Proposรฉ par %(brandname)s " + diff --git a/src/backend/locale/it_IT/LC_MESSAGES/django.po b/src/backend/locale/it_IT/LC_MESSAGES/django.po new file mode 100644 index 0000000..78e68fa --- /dev/null +++ b/src/backend/locale/it_IT/LC_MESSAGES/django.po @@ -0,0 +1,390 @@ +msgid "" +msgstr "" +"Project-Id-Version: lasuite-docs\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-05-22 12:09+0000\n" +"PO-Revision-Date: 2025-05-22 14:16\n" +"Last-Translator: \n" +"Language-Team: Italian\n" +"Language: it_IT\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Crowdin-Project: lasuite-docs\n" +"X-Crowdin-Project-ID: 754523\n" +"X-Crowdin-Language: it\n" +"X-Crowdin-File: backend-conversations.pot\n" +"X-Crowdin-File-ID: 18\n" + +#: build/lib/core/admin.py:37 core/admin.py:37 +msgid "Personal info" +msgstr "Informazioni personali" + +#: build/lib/core/admin.py:50 build/lib/core/admin.py:138 core/admin.py:50 +#: core/admin.py:138 +msgid "Permissions" +msgstr "Permessi" + +#: build/lib/core/admin.py:62 core/admin.py:62 +msgid "Important dates" +msgstr "Date importanti" + +#: build/lib/core/admin.py:148 core/admin.py:148 +msgid "Tree structure" +msgstr "Struttura ad albero" + +#: build/lib/core/api/filters.py:47 core/api/filters.py:47 +msgid "Title" +msgstr "Titolo" + +#: build/lib/core/api/filters.py:61 core/api/filters.py:61 +msgid "Creator is me" +msgstr "Il creatore sono io" + +#: build/lib/core/api/filters.py:64 core/api/filters.py:64 +msgid "Favorite" +msgstr "Preferiti" + +#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446 +msgid "A new document was created on your behalf!" +msgstr "Un nuovo documento รจ stato creato a tuo nome!" + +#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450 +msgid "You have been granted ownership of a new document:" +msgstr "Sei ora proprietario di un nuovo documento:" + +#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586 +msgid "Body" +msgstr "Corpo" + +#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589 +msgid "Body type" +msgstr "" + +#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595 +msgid "Format" +msgstr "Formato" + +#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967 +#, python-brace-format +msgid "copy of {title}" +msgstr "copia di {title}" + +#: build/lib/core/enums.py:36 core/enums.py:36 +msgid "First child" +msgstr "" + +#: build/lib/core/enums.py:37 core/enums.py:37 +msgid "Last child" +msgstr "" + +#: build/lib/core/enums.py:38 core/enums.py:38 +msgid "First sibling" +msgstr "" + +#: build/lib/core/enums.py:39 core/enums.py:39 +msgid "Last sibling" +msgstr "" + +#: build/lib/core/enums.py:40 core/enums.py:40 +msgid "Left" +msgstr "Sinistra" + +#: build/lib/core/enums.py:41 core/enums.py:41 +msgid "Right" +msgstr "Destra" + +#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56 +#: core/models.py:63 +msgid "Reader" +msgstr "Lettore" + +#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57 +#: core/models.py:64 +msgid "Editor" +msgstr "Editor" + +#: build/lib/core/models.py:65 core/models.py:65 +msgid "Administrator" +msgstr "Amministratore" + +#: build/lib/core/models.py:66 core/models.py:66 +msgid "Owner" +msgstr "Proprietario" + +#: build/lib/core/models.py:77 core/models.py:77 +msgid "Restricted" +msgstr "Limitato" + +#: build/lib/core/models.py:81 core/models.py:81 +msgid "Authenticated" +msgstr "Autenticato" + +#: build/lib/core/models.py:83 core/models.py:83 +msgid "Public" +msgstr "Pubblico" + +#: build/lib/core/models.py:154 core/models.py:154 +msgid "id" +msgstr "Id" + +#: build/lib/core/models.py:155 core/models.py:155 +msgid "primary key for the record as UUID" +msgstr "chiave primaria per il record come UUID" + +#: build/lib/core/models.py:161 core/models.py:161 +msgid "created on" +msgstr "creato il" + +#: build/lib/core/models.py:162 core/models.py:162 +msgid "date and time at which a record was created" +msgstr "data e ora in cui รจ stato creato un record" + +#: build/lib/core/models.py:167 core/models.py:167 +msgid "updated on" +msgstr "aggiornato il" + +#: build/lib/core/models.py:168 core/models.py:168 +msgid "date and time at which a record was last updated" +msgstr "data e ora in cui lโ€™ultimo record รจ stato aggiornato" + +#: build/lib/core/models.py:204 core/models.py:204 +msgid "We couldn't find a user with this sub but the email is already associated with a registered user." +msgstr "" + +#: build/lib/core/models.py:217 core/models.py:217 +msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters." +msgstr "" + +#: build/lib/core/models.py:223 core/models.py:223 +msgid "sub" +msgstr "" + +#: build/lib/core/models.py:225 core/models.py:225 +msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only." +msgstr "Richiesto. 255 caratteri o meno. Solo lettere, numeri e @/./+/-/_/: caratteri." + +#: build/lib/core/models.py:234 core/models.py:234 +msgid "full name" +msgstr "nome completo" + +#: build/lib/core/models.py:235 core/models.py:235 +msgid "short name" +msgstr "nome" + +#: build/lib/core/models.py:237 core/models.py:237 +msgid "identity email address" +msgstr "indirizzo email di identitร " + +#: build/lib/core/models.py:242 core/models.py:242 +msgid "admin email address" +msgstr "Indirizzo email dell'amministratore" + +#: build/lib/core/models.py:249 core/models.py:249 +msgid "language" +msgstr "lingua" + +#: build/lib/core/models.py:250 core/models.py:250 +msgid "The language in which the user wants to see the interface." +msgstr "La lingua in cui l'utente vuole vedere l'interfaccia." + +#: build/lib/core/models.py:258 core/models.py:258 +msgid "The timezone in which the user wants to see times." +msgstr "Il fuso orario in cui l'utente vuole vedere gli orari." + +#: build/lib/core/models.py:261 core/models.py:261 +msgid "device" +msgstr "dispositivo" + +#: build/lib/core/models.py:263 core/models.py:263 +msgid "Whether the user is a device or a real user." +msgstr "Se l'utente รจ un dispositivo o un utente reale." + +#: build/lib/core/models.py:266 core/models.py:266 +msgid "staff status" +msgstr "stato del personale" + +#: build/lib/core/models.py:268 core/models.py:268 +msgid "Whether the user can log into this admin site." +msgstr "Indica se l'utente puรฒ accedere a questo sito amministratore." + +#: build/lib/core/models.py:271 core/models.py:271 +msgid "active" +msgstr "attivo" + +#: build/lib/core/models.py:274 core/models.py:274 +msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts." +msgstr "Indica se questo utente deve essere trattato come attivo. Deseleziona invece di eliminare gli account." + +#: build/lib/core/models.py:286 core/models.py:286 +msgid "user" +msgstr "utente" + +#: build/lib/core/models.py:287 core/models.py:287 +msgid "users" +msgstr "utenti" + +#: build/lib/core/models.py:470 build/lib/core/models.py:1155 +#: core/models.py:470 core/models.py:1155 +msgid "title" +msgstr "titolo" + +#: build/lib/core/models.py:471 core/models.py:471 +msgid "excerpt" +msgstr "" + +#: build/lib/core/models.py:519 core/models.py:519 +msgid "Document" +msgstr "Documento" + +#: build/lib/core/models.py:520 core/models.py:520 +msgid "Documents" +msgstr "Documenti" + +#: build/lib/core/models.py:532 build/lib/core/models.py:873 core/models.py:532 +#: core/models.py:873 +msgid "Untitled Document" +msgstr "Documento senza titolo" + +#: build/lib/core/models.py:908 core/models.py:908 +#, python-brace-format +msgid "{name} shared a document with you!" +msgstr "{name} ha condiviso un documento con te!" + +#: build/lib/core/models.py:912 core/models.py:912 +#, python-brace-format +msgid "{name} invited you with the role \"{role}\" on the following document:" +msgstr "{name} ti ha invitato con il ruolo \"{role}\" nel seguente documento:" + +#: build/lib/core/models.py:918 core/models.py:918 +#, python-brace-format +msgid "{name} shared a document with you: {title}" +msgstr "{name} ha condiviso un documento con te: {title}" + +#: build/lib/core/models.py:1016 core/models.py:1016 +msgid "Document/user link trace" +msgstr "" + +#: build/lib/core/models.py:1017 core/models.py:1017 +msgid "Document/user link traces" +msgstr "" + +#: build/lib/core/models.py:1023 core/models.py:1023 +msgid "A link trace already exists for this document/user." +msgstr "" + +#: build/lib/core/models.py:1046 core/models.py:1046 +msgid "Document favorite" +msgstr "Documento preferito" + +#: build/lib/core/models.py:1047 core/models.py:1047 +msgid "Document favorites" +msgstr "Documenti preferiti" + +#: build/lib/core/models.py:1053 core/models.py:1053 +msgid "This document is already targeted by a favorite relation instance for the same user." +msgstr "" + +#: build/lib/core/models.py:1075 core/models.py:1075 +msgid "Document/user relation" +msgstr "" + +#: build/lib/core/models.py:1076 core/models.py:1076 +msgid "Document/user relations" +msgstr "" + +#: build/lib/core/models.py:1082 core/models.py:1082 +msgid "This user is already in this document." +msgstr "Questo utente รจ giร  presente in questo documento." + +#: build/lib/core/models.py:1088 core/models.py:1088 +msgid "This team is already in this document." +msgstr "Questo team รจ giร  presente in questo documento." + +#: build/lib/core/models.py:1094 build/lib/core/models.py:1242 +#: core/models.py:1094 core/models.py:1242 +msgid "Either user or team must be set, not both." +msgstr "" + +#: build/lib/core/models.py:1156 core/models.py:1156 +msgid "description" +msgstr "descrizione" + +#: build/lib/core/models.py:1157 core/models.py:1157 +msgid "code" +msgstr "code" + +#: build/lib/core/models.py:1158 core/models.py:1158 +msgid "css" +msgstr "css" + +#: build/lib/core/models.py:1160 core/models.py:1160 +msgid "public" +msgstr "pubblico" + +#: build/lib/core/models.py:1162 core/models.py:1162 +msgid "Whether this template is public for anyone to use." +msgstr "Indica se questo modello รจ pubblico per chiunque." + +#: build/lib/core/models.py:1168 core/models.py:1168 +msgid "Template" +msgstr "Modello" + +#: build/lib/core/models.py:1169 core/models.py:1169 +msgid "Templates" +msgstr "Modelli" + +#: build/lib/core/models.py:1223 core/models.py:1223 +msgid "Template/user relation" +msgstr "" + +#: build/lib/core/models.py:1224 core/models.py:1224 +msgid "Template/user relations" +msgstr "" + +#: build/lib/core/models.py:1230 core/models.py:1230 +msgid "This user is already in this template." +msgstr "Questo utente รจ giร  in questo modello." + +#: build/lib/core/models.py:1236 core/models.py:1236 +msgid "This team is already in this template." +msgstr "Questo team รจ giร  in questo modello." + +#: build/lib/core/models.py:1259 core/models.py:1259 +msgid "email address" +msgstr "indirizzo e-mail" + +#: build/lib/core/models.py:1278 core/models.py:1278 +msgid "Document invitation" +msgstr "Invito al documento" + +#: build/lib/core/models.py:1279 core/models.py:1279 +msgid "Document invitations" +msgstr "Inviti al documento" + +#: build/lib/core/models.py:1299 core/models.py:1299 +msgid "This email is already associated to a registered user." +msgstr "Questa email รจ giร  associata a un utente registrato." + +#: core/templates/mail/html/invitation.html:162 +#: core/templates/mail/text/invitation.txt:3 +msgid "Logo email" +msgstr "Logo e-mail" + +#: core/templates/mail/html/invitation.html:209 +#: core/templates/mail/text/invitation.txt:10 +msgid "Open" +msgstr "Apri" + +#: core/templates/mail/html/invitation.html:226 +#: core/templates/mail/text/invitation.txt:14 +msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. " +msgstr "" + +#: core/templates/mail/html/invitation.html:233 +#: core/templates/mail/text/invitation.txt:16 +#, python-format +msgid " Brought to you by %(brandname)s " +msgstr "" + diff --git a/src/backend/locale/nl_NL/LC_MESSAGES/django.po b/src/backend/locale/nl_NL/LC_MESSAGES/django.po new file mode 100644 index 0000000..c6bc4ac --- /dev/null +++ b/src/backend/locale/nl_NL/LC_MESSAGES/django.po @@ -0,0 +1,390 @@ +msgid "" +msgstr "" +"Project-Id-Version: lasuite-docs\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-05-22 12:09+0000\n" +"PO-Revision-Date: 2025-05-22 14:16\n" +"Last-Translator: \n" +"Language-Team: Dutch\n" +"Language: nl_NL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Crowdin-Project: lasuite-docs\n" +"X-Crowdin-Project-ID: 754523\n" +"X-Crowdin-Language: nl\n" +"X-Crowdin-File: backend-conversations.pot\n" +"X-Crowdin-File-ID: 18\n" + +#: build/lib/core/admin.py:37 core/admin.py:37 +msgid "Personal info" +msgstr "Persoonlijke informatie" + +#: build/lib/core/admin.py:50 build/lib/core/admin.py:138 core/admin.py:50 +#: core/admin.py:138 +msgid "Permissions" +msgstr "Toestemmingen" + +#: build/lib/core/admin.py:62 core/admin.py:62 +msgid "Important dates" +msgstr "Belangrijke datums" + +#: build/lib/core/admin.py:148 core/admin.py:148 +msgid "Tree structure" +msgstr "Document structuur" + +#: build/lib/core/api/filters.py:47 core/api/filters.py:47 +msgid "Title" +msgstr "Titel" + +#: build/lib/core/api/filters.py:61 core/api/filters.py:61 +msgid "Creator is me" +msgstr "Ik ben Eigenaar" + +#: build/lib/core/api/filters.py:64 core/api/filters.py:64 +msgid "Favorite" +msgstr "Favoriete" + +#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446 +msgid "A new document was created on your behalf!" +msgstr "Een nieuw document was gecreรซerd voor u!" + +#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450 +msgid "You have been granted ownership of a new document:" +msgstr "U heeft eigenaarschap van een nieuw document:" + +#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586 +msgid "Body" +msgstr "Text" + +#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589 +msgid "Body type" +msgstr "Text type" + +#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595 +msgid "Format" +msgstr "Formaat" + +#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967 +#, python-brace-format +msgid "copy of {title}" +msgstr "kopie van {title}" + +#: build/lib/core/enums.py:36 core/enums.py:36 +msgid "First child" +msgstr "Eerste node" + +#: build/lib/core/enums.py:37 core/enums.py:37 +msgid "Last child" +msgstr "Laatste node" + +#: build/lib/core/enums.py:38 core/enums.py:38 +msgid "First sibling" +msgstr "Eerste naaste" + +#: build/lib/core/enums.py:39 core/enums.py:39 +msgid "Last sibling" +msgstr "Laatste naaste" + +#: build/lib/core/enums.py:40 core/enums.py:40 +msgid "Left" +msgstr "Links" + +#: build/lib/core/enums.py:41 core/enums.py:41 +msgid "Right" +msgstr "Rechts" + +#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56 +#: core/models.py:63 +msgid "Reader" +msgstr "Lezer" + +#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57 +#: core/models.py:64 +msgid "Editor" +msgstr "Bewerker" + +#: build/lib/core/models.py:65 core/models.py:65 +msgid "Administrator" +msgstr "Administrator" + +#: build/lib/core/models.py:66 core/models.py:66 +msgid "Owner" +msgstr "Eigenaar" + +#: build/lib/core/models.py:77 core/models.py:77 +msgid "Restricted" +msgstr "Niet toegestaan" + +#: build/lib/core/models.py:81 core/models.py:81 +msgid "Authenticated" +msgstr "Geauthenticeerd" + +#: build/lib/core/models.py:83 core/models.py:83 +msgid "Public" +msgstr "Publiek" + +#: build/lib/core/models.py:154 core/models.py:154 +msgid "id" +msgstr "id" + +#: build/lib/core/models.py:155 core/models.py:155 +msgid "primary key for the record as UUID" +msgstr "primaire sleutel voor dossier als UUID" + +#: build/lib/core/models.py:161 core/models.py:161 +msgid "created on" +msgstr "gemaakt op" + +#: build/lib/core/models.py:162 core/models.py:162 +msgid "date and time at which a record was created" +msgstr "datum en tijd wanneer dossier was gecreรซerd" + +#: build/lib/core/models.py:167 core/models.py:167 +msgid "updated on" +msgstr "Laatst gewijzigd op" + +#: build/lib/core/models.py:168 core/models.py:168 +msgid "date and time at which a record was last updated" +msgstr "datum en tijd waarop dossier laatst was gewijzigd" + +#: build/lib/core/models.py:204 core/models.py:204 +msgid "We couldn't find a user with this sub but the email is already associated with a registered user." +msgstr "Wij konden geen gebruiker vinden met deze id, maar de email is al geassocieerd met een geregistreerde gebruiker." + +#: build/lib/core/models.py:217 core/models.py:217 +msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters." +msgstr ".Geef een valide id. De waarde mag alleen letters, nummers en @/./.+/-/_: karakters bevatten." + +#: build/lib/core/models.py:223 core/models.py:223 +msgid "sub" +msgstr "id" + +#: build/lib/core/models.py:225 core/models.py:225 +msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only." +msgstr "Verplicht. 255 karakters of minder. Alleen letters, nummers en @/./+/-/_/: karakters zijn toegestaan." + +#: build/lib/core/models.py:234 core/models.py:234 +msgid "full name" +msgstr "volledige naam" + +#: build/lib/core/models.py:235 core/models.py:235 +msgid "short name" +msgstr "gebruikersnaam" + +#: build/lib/core/models.py:237 core/models.py:237 +msgid "identity email address" +msgstr "identiteit email adres" + +#: build/lib/core/models.py:242 core/models.py:242 +msgid "admin email address" +msgstr "admin email adres" + +#: build/lib/core/models.py:249 core/models.py:249 +msgid "language" +msgstr "taal" + +#: build/lib/core/models.py:250 core/models.py:250 +msgid "The language in which the user wants to see the interface." +msgstr "De taal waarin de gebruiker de interface wilt zien." + +#: build/lib/core/models.py:258 core/models.py:258 +msgid "The timezone in which the user wants to see times." +msgstr "De tijdzone waarin de gebruiker de tijden wilt zien." + +#: build/lib/core/models.py:261 core/models.py:261 +msgid "device" +msgstr "apparaat" + +#: build/lib/core/models.py:263 core/models.py:263 +msgid "Whether the user is a device or a real user." +msgstr "Of de gebruiker een apparaat is of een echte gebruiker." + +#: build/lib/core/models.py:266 core/models.py:266 +msgid "staff status" +msgstr "beheerder status" + +#: build/lib/core/models.py:268 core/models.py:268 +msgid "Whether the user can log into this admin site." +msgstr "Of de gebruiker kan inloggen in het admin gedeelte." + +#: build/lib/core/models.py:271 core/models.py:271 +msgid "active" +msgstr "actief" + +#: build/lib/core/models.py:274 core/models.py:274 +msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts." +msgstr "Of een gebruiker als actief moet worden beschouwd. Deselecteer dit in plaats van het account te deleten." + +#: build/lib/core/models.py:286 core/models.py:286 +msgid "user" +msgstr "gebruiker" + +#: build/lib/core/models.py:287 core/models.py:287 +msgid "users" +msgstr "gebruikers" + +#: build/lib/core/models.py:470 build/lib/core/models.py:1155 +#: core/models.py:470 core/models.py:1155 +msgid "title" +msgstr "titel" + +#: build/lib/core/models.py:471 core/models.py:471 +msgid "excerpt" +msgstr "uittreksel" + +#: build/lib/core/models.py:519 core/models.py:519 +msgid "Document" +msgstr "Document" + +#: build/lib/core/models.py:520 core/models.py:520 +msgid "Documents" +msgstr "Documenten" + +#: build/lib/core/models.py:532 build/lib/core/models.py:873 core/models.py:532 +#: core/models.py:873 +msgid "Untitled Document" +msgstr "Naamloos Document" + +#: build/lib/core/models.py:908 core/models.py:908 +#, python-brace-format +msgid "{name} shared a document with you!" +msgstr "{name} heeft een document met gedeeld!" + +#: build/lib/core/models.py:912 core/models.py:912 +#, python-brace-format +msgid "{name} invited you with the role \"{role}\" on the following document:" +msgstr "{name} heeft u uitgenodigd met de rol \"{role}\" op het volgende document:" + +#: build/lib/core/models.py:918 core/models.py:918 +#, python-brace-format +msgid "{name} shared a document with you: {title}" +msgstr "{name} heeft een document met u gedeeld: {title}" + +#: build/lib/core/models.py:1016 core/models.py:1016 +msgid "Document/user link trace" +msgstr "Document/gebruiker url" + +#: build/lib/core/models.py:1017 core/models.py:1017 +msgid "Document/user link traces" +msgstr "Document/gebruiker url" + +#: build/lib/core/models.py:1023 core/models.py:1023 +msgid "A link trace already exists for this document/user." +msgstr "Een url bestaat al voor dit document/deze gebruiker." + +#: build/lib/core/models.py:1046 core/models.py:1046 +msgid "Document favorite" +msgstr "Document favoriet" + +#: build/lib/core/models.py:1047 core/models.py:1047 +msgid "Document favorites" +msgstr "Document favorieten" + +#: build/lib/core/models.py:1053 core/models.py:1053 +msgid "This document is already targeted by a favorite relation instance for the same user." +msgstr "Dit document is al in gebruik als favoriete door dezelfde gebruiker." + +#: build/lib/core/models.py:1075 core/models.py:1075 +msgid "Document/user relation" +msgstr "Document/gebruiker relatie" + +#: build/lib/core/models.py:1076 core/models.py:1076 +msgid "Document/user relations" +msgstr "Document/gebruiker relaties" + +#: build/lib/core/models.py:1082 core/models.py:1082 +msgid "This user is already in this document." +msgstr "De gebruiker is al in dit document." + +#: build/lib/core/models.py:1088 core/models.py:1088 +msgid "This team is already in this document." +msgstr "Het team is al in dit document." + +#: build/lib/core/models.py:1094 build/lib/core/models.py:1242 +#: core/models.py:1094 core/models.py:1242 +msgid "Either user or team must be set, not both." +msgstr "Een gebruiker of team moet gekozen worden, maar niet beide." + +#: build/lib/core/models.py:1156 core/models.py:1156 +msgid "description" +msgstr "omschrijving" + +#: build/lib/core/models.py:1157 core/models.py:1157 +msgid "code" +msgstr "code" + +#: build/lib/core/models.py:1158 core/models.py:1158 +msgid "css" +msgstr "css" + +#: build/lib/core/models.py:1160 core/models.py:1160 +msgid "public" +msgstr "publiek" + +#: build/lib/core/models.py:1162 core/models.py:1162 +msgid "Whether this template is public for anyone to use." +msgstr "Of dit template als publiek is en door iedereen te gebruiken is." + +#: build/lib/core/models.py:1168 core/models.py:1168 +msgid "Template" +msgstr "Template" + +#: build/lib/core/models.py:1169 core/models.py:1169 +msgid "Templates" +msgstr "Templates" + +#: build/lib/core/models.py:1223 core/models.py:1223 +msgid "Template/user relation" +msgstr "Template/gebruiker relatie" + +#: build/lib/core/models.py:1224 core/models.py:1224 +msgid "Template/user relations" +msgstr "Template/gebruiker relaties" + +#: build/lib/core/models.py:1230 core/models.py:1230 +msgid "This user is already in this template." +msgstr "De gebruiker bestaat al in dit template." + +#: build/lib/core/models.py:1236 core/models.py:1236 +msgid "This team is already in this template." +msgstr "Het team bestaat al in dit template." + +#: build/lib/core/models.py:1259 core/models.py:1259 +msgid "email address" +msgstr "email adres" + +#: build/lib/core/models.py:1278 core/models.py:1278 +msgid "Document invitation" +msgstr "Document uitnodiging" + +#: build/lib/core/models.py:1279 core/models.py:1279 +msgid "Document invitations" +msgstr "Document uitnodigingen" + +#: build/lib/core/models.py:1299 core/models.py:1299 +msgid "This email is already associated to a registered user." +msgstr "Deze email is al geassocieerd met een geregistreerde gebruiker." + +#: core/templates/mail/html/invitation.html:162 +#: core/templates/mail/text/invitation.txt:3 +msgid "Logo email" +msgstr "Logo email" + +#: core/templates/mail/html/invitation.html:209 +#: core/templates/mail/text/invitation.txt:10 +msgid "Open" +msgstr "Open" + +#: core/templates/mail/html/invitation.html:226 +#: core/templates/mail/text/invitation.txt:14 +msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. " +msgstr " Docs, jouw nieuwe essentiรซle tool voor het organiseren, delen en collaboreren van documenten als team. " + +#: core/templates/mail/html/invitation.html:233 +#: core/templates/mail/text/invitation.txt:16 +#, python-format +msgid " Brought to you by %(brandname)s " +msgstr " Geleverd door %(brandname)s " + diff --git a/src/backend/locale/pt_PT/LC_MESSAGES/django.po b/src/backend/locale/pt_PT/LC_MESSAGES/django.po new file mode 100644 index 0000000..9124f5f --- /dev/null +++ b/src/backend/locale/pt_PT/LC_MESSAGES/django.po @@ -0,0 +1,390 @@ +msgid "" +msgstr "" +"Project-Id-Version: lasuite-docs\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-05-22 12:09+0000\n" +"PO-Revision-Date: 2025-05-22 14:16\n" +"Last-Translator: \n" +"Language-Team: Portuguese\n" +"Language: pt_PT\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Crowdin-Project: lasuite-docs\n" +"X-Crowdin-Project-ID: 754523\n" +"X-Crowdin-Language: pt-PT\n" +"X-Crowdin-File: backend-conversations.pot\n" +"X-Crowdin-File-ID: 18\n" + +#: build/lib/core/admin.py:37 core/admin.py:37 +msgid "Personal info" +msgstr "" + +#: build/lib/core/admin.py:50 build/lib/core/admin.py:138 core/admin.py:50 +#: core/admin.py:138 +msgid "Permissions" +msgstr "" + +#: build/lib/core/admin.py:62 core/admin.py:62 +msgid "Important dates" +msgstr "" + +#: build/lib/core/admin.py:148 core/admin.py:148 +msgid "Tree structure" +msgstr "" + +#: build/lib/core/api/filters.py:47 core/api/filters.py:47 +msgid "Title" +msgstr "" + +#: build/lib/core/api/filters.py:61 core/api/filters.py:61 +msgid "Creator is me" +msgstr "" + +#: build/lib/core/api/filters.py:64 core/api/filters.py:64 +msgid "Favorite" +msgstr "" + +#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446 +msgid "A new document was created on your behalf!" +msgstr "" + +#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450 +msgid "You have been granted ownership of a new document:" +msgstr "" + +#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586 +msgid "Body" +msgstr "" + +#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589 +msgid "Body type" +msgstr "" + +#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595 +msgid "Format" +msgstr "" + +#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967 +#, python-brace-format +msgid "copy of {title}" +msgstr "" + +#: build/lib/core/enums.py:36 core/enums.py:36 +msgid "First child" +msgstr "" + +#: build/lib/core/enums.py:37 core/enums.py:37 +msgid "Last child" +msgstr "" + +#: build/lib/core/enums.py:38 core/enums.py:38 +msgid "First sibling" +msgstr "" + +#: build/lib/core/enums.py:39 core/enums.py:39 +msgid "Last sibling" +msgstr "" + +#: build/lib/core/enums.py:40 core/enums.py:40 +msgid "Left" +msgstr "" + +#: build/lib/core/enums.py:41 core/enums.py:41 +msgid "Right" +msgstr "" + +#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56 +#: core/models.py:63 +msgid "Reader" +msgstr "" + +#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57 +#: core/models.py:64 +msgid "Editor" +msgstr "" + +#: build/lib/core/models.py:65 core/models.py:65 +msgid "Administrator" +msgstr "" + +#: build/lib/core/models.py:66 core/models.py:66 +msgid "Owner" +msgstr "" + +#: build/lib/core/models.py:77 core/models.py:77 +msgid "Restricted" +msgstr "" + +#: build/lib/core/models.py:81 core/models.py:81 +msgid "Authenticated" +msgstr "" + +#: build/lib/core/models.py:83 core/models.py:83 +msgid "Public" +msgstr "" + +#: build/lib/core/models.py:154 core/models.py:154 +msgid "id" +msgstr "" + +#: build/lib/core/models.py:155 core/models.py:155 +msgid "primary key for the record as UUID" +msgstr "" + +#: build/lib/core/models.py:161 core/models.py:161 +msgid "created on" +msgstr "" + +#: build/lib/core/models.py:162 core/models.py:162 +msgid "date and time at which a record was created" +msgstr "" + +#: build/lib/core/models.py:167 core/models.py:167 +msgid "updated on" +msgstr "" + +#: build/lib/core/models.py:168 core/models.py:168 +msgid "date and time at which a record was last updated" +msgstr "" + +#: build/lib/core/models.py:204 core/models.py:204 +msgid "We couldn't find a user with this sub but the email is already associated with a registered user." +msgstr "" + +#: build/lib/core/models.py:217 core/models.py:217 +msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters." +msgstr "" + +#: build/lib/core/models.py:223 core/models.py:223 +msgid "sub" +msgstr "" + +#: build/lib/core/models.py:225 core/models.py:225 +msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only." +msgstr "" + +#: build/lib/core/models.py:234 core/models.py:234 +msgid "full name" +msgstr "" + +#: build/lib/core/models.py:235 core/models.py:235 +msgid "short name" +msgstr "" + +#: build/lib/core/models.py:237 core/models.py:237 +msgid "identity email address" +msgstr "" + +#: build/lib/core/models.py:242 core/models.py:242 +msgid "admin email address" +msgstr "" + +#: build/lib/core/models.py:249 core/models.py:249 +msgid "language" +msgstr "" + +#: build/lib/core/models.py:250 core/models.py:250 +msgid "The language in which the user wants to see the interface." +msgstr "" + +#: build/lib/core/models.py:258 core/models.py:258 +msgid "The timezone in which the user wants to see times." +msgstr "" + +#: build/lib/core/models.py:261 core/models.py:261 +msgid "device" +msgstr "" + +#: build/lib/core/models.py:263 core/models.py:263 +msgid "Whether the user is a device or a real user." +msgstr "" + +#: build/lib/core/models.py:266 core/models.py:266 +msgid "staff status" +msgstr "" + +#: build/lib/core/models.py:268 core/models.py:268 +msgid "Whether the user can log into this admin site." +msgstr "" + +#: build/lib/core/models.py:271 core/models.py:271 +msgid "active" +msgstr "" + +#: build/lib/core/models.py:274 core/models.py:274 +msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts." +msgstr "" + +#: build/lib/core/models.py:286 core/models.py:286 +msgid "user" +msgstr "" + +#: build/lib/core/models.py:287 core/models.py:287 +msgid "users" +msgstr "" + +#: build/lib/core/models.py:470 build/lib/core/models.py:1155 +#: core/models.py:470 core/models.py:1155 +msgid "title" +msgstr "" + +#: build/lib/core/models.py:471 core/models.py:471 +msgid "excerpt" +msgstr "" + +#: build/lib/core/models.py:519 core/models.py:519 +msgid "Document" +msgstr "" + +#: build/lib/core/models.py:520 core/models.py:520 +msgid "Documents" +msgstr "" + +#: build/lib/core/models.py:532 build/lib/core/models.py:873 core/models.py:532 +#: core/models.py:873 +msgid "Untitled Document" +msgstr "" + +#: build/lib/core/models.py:908 core/models.py:908 +#, python-brace-format +msgid "{name} shared a document with you!" +msgstr "" + +#: build/lib/core/models.py:912 core/models.py:912 +#, python-brace-format +msgid "{name} invited you with the role \"{role}\" on the following document:" +msgstr "" + +#: build/lib/core/models.py:918 core/models.py:918 +#, python-brace-format +msgid "{name} shared a document with you: {title}" +msgstr "" + +#: build/lib/core/models.py:1016 core/models.py:1016 +msgid "Document/user link trace" +msgstr "" + +#: build/lib/core/models.py:1017 core/models.py:1017 +msgid "Document/user link traces" +msgstr "" + +#: build/lib/core/models.py:1023 core/models.py:1023 +msgid "A link trace already exists for this document/user." +msgstr "" + +#: build/lib/core/models.py:1046 core/models.py:1046 +msgid "Document favorite" +msgstr "" + +#: build/lib/core/models.py:1047 core/models.py:1047 +msgid "Document favorites" +msgstr "" + +#: build/lib/core/models.py:1053 core/models.py:1053 +msgid "This document is already targeted by a favorite relation instance for the same user." +msgstr "" + +#: build/lib/core/models.py:1075 core/models.py:1075 +msgid "Document/user relation" +msgstr "" + +#: build/lib/core/models.py:1076 core/models.py:1076 +msgid "Document/user relations" +msgstr "" + +#: build/lib/core/models.py:1082 core/models.py:1082 +msgid "This user is already in this document." +msgstr "" + +#: build/lib/core/models.py:1088 core/models.py:1088 +msgid "This team is already in this document." +msgstr "" + +#: build/lib/core/models.py:1094 build/lib/core/models.py:1242 +#: core/models.py:1094 core/models.py:1242 +msgid "Either user or team must be set, not both." +msgstr "" + +#: build/lib/core/models.py:1156 core/models.py:1156 +msgid "description" +msgstr "" + +#: build/lib/core/models.py:1157 core/models.py:1157 +msgid "code" +msgstr "" + +#: build/lib/core/models.py:1158 core/models.py:1158 +msgid "css" +msgstr "" + +#: build/lib/core/models.py:1160 core/models.py:1160 +msgid "public" +msgstr "" + +#: build/lib/core/models.py:1162 core/models.py:1162 +msgid "Whether this template is public for anyone to use." +msgstr "" + +#: build/lib/core/models.py:1168 core/models.py:1168 +msgid "Template" +msgstr "" + +#: build/lib/core/models.py:1169 core/models.py:1169 +msgid "Templates" +msgstr "" + +#: build/lib/core/models.py:1223 core/models.py:1223 +msgid "Template/user relation" +msgstr "" + +#: build/lib/core/models.py:1224 core/models.py:1224 +msgid "Template/user relations" +msgstr "" + +#: build/lib/core/models.py:1230 core/models.py:1230 +msgid "This user is already in this template." +msgstr "" + +#: build/lib/core/models.py:1236 core/models.py:1236 +msgid "This team is already in this template." +msgstr "" + +#: build/lib/core/models.py:1259 core/models.py:1259 +msgid "email address" +msgstr "" + +#: build/lib/core/models.py:1278 core/models.py:1278 +msgid "Document invitation" +msgstr "" + +#: build/lib/core/models.py:1279 core/models.py:1279 +msgid "Document invitations" +msgstr "" + +#: build/lib/core/models.py:1299 core/models.py:1299 +msgid "This email is already associated to a registered user." +msgstr "" + +#: core/templates/mail/html/invitation.html:162 +#: core/templates/mail/text/invitation.txt:3 +msgid "Logo email" +msgstr "" + +#: core/templates/mail/html/invitation.html:209 +#: core/templates/mail/text/invitation.txt:10 +msgid "Open" +msgstr "" + +#: core/templates/mail/html/invitation.html:226 +#: core/templates/mail/text/invitation.txt:14 +msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. " +msgstr "" + +#: core/templates/mail/html/invitation.html:233 +#: core/templates/mail/text/invitation.txt:16 +#, python-format +msgid " Brought to you by %(brandname)s " +msgstr "" + diff --git a/src/backend/locale/sl_SI/LC_MESSAGES/django.po b/src/backend/locale/sl_SI/LC_MESSAGES/django.po new file mode 100644 index 0000000..623ca3c --- /dev/null +++ b/src/backend/locale/sl_SI/LC_MESSAGES/django.po @@ -0,0 +1,390 @@ +msgid "" +msgstr "" +"Project-Id-Version: lasuite-docs\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-05-22 12:09+0000\n" +"PO-Revision-Date: 2025-05-22 14:16\n" +"Last-Translator: \n" +"Language-Team: Slovenian\n" +"Language: sl_SI\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;\n" +"X-Crowdin-Project: lasuite-docs\n" +"X-Crowdin-Project-ID: 754523\n" +"X-Crowdin-Language: sl\n" +"X-Crowdin-File: backend-conversations.pot\n" +"X-Crowdin-File-ID: 18\n" + +#: build/lib/core/admin.py:37 core/admin.py:37 +msgid "Personal info" +msgstr "Osebni podatki" + +#: build/lib/core/admin.py:50 build/lib/core/admin.py:138 core/admin.py:50 +#: core/admin.py:138 +msgid "Permissions" +msgstr "Dovoljenja" + +#: build/lib/core/admin.py:62 core/admin.py:62 +msgid "Important dates" +msgstr "Pomembni datumi" + +#: build/lib/core/admin.py:148 core/admin.py:148 +msgid "Tree structure" +msgstr "Drevesna struktura" + +#: build/lib/core/api/filters.py:47 core/api/filters.py:47 +msgid "Title" +msgstr "Naslov" + +#: build/lib/core/api/filters.py:61 core/api/filters.py:61 +msgid "Creator is me" +msgstr "Ustvaril sem jaz" + +#: build/lib/core/api/filters.py:64 core/api/filters.py:64 +msgid "Favorite" +msgstr "Priljubljena" + +#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446 +msgid "A new document was created on your behalf!" +msgstr "Nov dokument je bil ustvarjen v vaลกem imenu!" + +#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450 +msgid "You have been granted ownership of a new document:" +msgstr "Dodeljeno vam je bilo lastniลกtvo nad novim dokumentom:" + +#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586 +msgid "Body" +msgstr "Telo" + +#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589 +msgid "Body type" +msgstr "Vrsta telesa" + +#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595 +msgid "Format" +msgstr "Oblika" + +#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967 +#, python-brace-format +msgid "copy of {title}" +msgstr "" + +#: build/lib/core/enums.py:36 core/enums.py:36 +msgid "First child" +msgstr "Prvi otrok" + +#: build/lib/core/enums.py:37 core/enums.py:37 +msgid "Last child" +msgstr "Zadnji otrok" + +#: build/lib/core/enums.py:38 core/enums.py:38 +msgid "First sibling" +msgstr "Prvi brat in sestra" + +#: build/lib/core/enums.py:39 core/enums.py:39 +msgid "Last sibling" +msgstr "Zadnji brat in sestra" + +#: build/lib/core/enums.py:40 core/enums.py:40 +msgid "Left" +msgstr "Levo" + +#: build/lib/core/enums.py:41 core/enums.py:41 +msgid "Right" +msgstr "Desno" + +#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56 +#: core/models.py:63 +msgid "Reader" +msgstr "Bralec" + +#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57 +#: core/models.py:64 +msgid "Editor" +msgstr "Urednik" + +#: build/lib/core/models.py:65 core/models.py:65 +msgid "Administrator" +msgstr "Skrbnik" + +#: build/lib/core/models.py:66 core/models.py:66 +msgid "Owner" +msgstr "Lastnik" + +#: build/lib/core/models.py:77 core/models.py:77 +msgid "Restricted" +msgstr "Omejeno" + +#: build/lib/core/models.py:81 core/models.py:81 +msgid "Authenticated" +msgstr "Preverjeno" + +#: build/lib/core/models.py:83 core/models.py:83 +msgid "Public" +msgstr "Javno" + +#: build/lib/core/models.py:154 core/models.py:154 +msgid "id" +msgstr "" + +#: build/lib/core/models.py:155 core/models.py:155 +msgid "primary key for the record as UUID" +msgstr "primarni kljuฤ za zapis kot UUID" + +#: build/lib/core/models.py:161 core/models.py:161 +msgid "created on" +msgstr "ustvarjen na" + +#: build/lib/core/models.py:162 core/models.py:162 +msgid "date and time at which a record was created" +msgstr "datum in ฤas, ko je bil zapis ustvarjen" + +#: build/lib/core/models.py:167 core/models.py:167 +msgid "updated on" +msgstr "posodobljeno dne" + +#: build/lib/core/models.py:168 core/models.py:168 +msgid "date and time at which a record was last updated" +msgstr "datum in ฤas, ko je bil zapis nazadnje posodobljen" + +#: build/lib/core/models.py:204 core/models.py:204 +msgid "We couldn't find a user with this sub but the email is already associated with a registered user." +msgstr "Nismo mogli najti uporabnika s tem sub, vendar je e-poลกtni naslov ลพe povezan z registriranim uporabnikom." + +#: build/lib/core/models.py:217 core/models.py:217 +msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters." +msgstr "Vnesite veljavno sub. Ta vrednost lahko vsebuje samo ฤrke, ลกtevilke in znake @/./+/-/_/:." + +#: build/lib/core/models.py:223 core/models.py:223 +msgid "sub" +msgstr "" + +#: build/lib/core/models.py:225 core/models.py:225 +msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only." +msgstr "Obvezno. 255 znakov ali manj. Samo ฤrke, ลกtevilke in znaki @/./+/-/_/: ." + +#: build/lib/core/models.py:234 core/models.py:234 +msgid "full name" +msgstr "polno ime" + +#: build/lib/core/models.py:235 core/models.py:235 +msgid "short name" +msgstr "kratko ime" + +#: build/lib/core/models.py:237 core/models.py:237 +msgid "identity email address" +msgstr "elektronski naslov identitete" + +#: build/lib/core/models.py:242 core/models.py:242 +msgid "admin email address" +msgstr "elektronski naslov skrbnika" + +#: build/lib/core/models.py:249 core/models.py:249 +msgid "language" +msgstr "jezik" + +#: build/lib/core/models.py:250 core/models.py:250 +msgid "The language in which the user wants to see the interface." +msgstr "Jezik, v katerem uporabnik ลพeli videti vmesnik." + +#: build/lib/core/models.py:258 core/models.py:258 +msgid "The timezone in which the user wants to see times." +msgstr "ฤŒasovni pas, v katerem ลพeli uporabnik videti uro." + +#: build/lib/core/models.py:261 core/models.py:261 +msgid "device" +msgstr "naprava" + +#: build/lib/core/models.py:263 core/models.py:263 +msgid "Whether the user is a device or a real user." +msgstr "Ali je uporabnik naprava ali pravi uporabnik." + +#: build/lib/core/models.py:266 core/models.py:266 +msgid "staff status" +msgstr "kadrovski status" + +#: build/lib/core/models.py:268 core/models.py:268 +msgid "Whether the user can log into this admin site." +msgstr "Ali se uporabnik lahko prijavi na to skrbniลกko mesto." + +#: build/lib/core/models.py:271 core/models.py:271 +msgid "active" +msgstr "aktivni" + +#: build/lib/core/models.py:274 core/models.py:274 +msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts." +msgstr "Ali je treba tega uporabnika obravnavati kot aktivnega. Namesto brisanja raฤunov poฤistite to izbiro." + +#: build/lib/core/models.py:286 core/models.py:286 +msgid "user" +msgstr "uporabnik" + +#: build/lib/core/models.py:287 core/models.py:287 +msgid "users" +msgstr "uporabniki" + +#: build/lib/core/models.py:470 build/lib/core/models.py:1155 +#: core/models.py:470 core/models.py:1155 +msgid "title" +msgstr "naslov" + +#: build/lib/core/models.py:471 core/models.py:471 +msgid "excerpt" +msgstr "odlomek" + +#: build/lib/core/models.py:519 core/models.py:519 +msgid "Document" +msgstr "Dokument" + +#: build/lib/core/models.py:520 core/models.py:520 +msgid "Documents" +msgstr "Dokumenti" + +#: build/lib/core/models.py:532 build/lib/core/models.py:873 core/models.py:532 +#: core/models.py:873 +msgid "Untitled Document" +msgstr "Dokument brez naslova" + +#: build/lib/core/models.py:908 core/models.py:908 +#, python-brace-format +msgid "{name} shared a document with you!" +msgstr "{name} je delil dokument z vami!" + +#: build/lib/core/models.py:912 core/models.py:912 +#, python-brace-format +msgid "{name} invited you with the role \"{role}\" on the following document:" +msgstr "{name} vas je povabil z vlogo \"{role}\" na naslednjem dokumentu:" + +#: build/lib/core/models.py:918 core/models.py:918 +#, python-brace-format +msgid "{name} shared a document with you: {title}" +msgstr "{name} je delil dokument z vami: {title}" + +#: build/lib/core/models.py:1016 core/models.py:1016 +msgid "Document/user link trace" +msgstr "Dokument/sled povezave uporabnika" + +#: build/lib/core/models.py:1017 core/models.py:1017 +msgid "Document/user link traces" +msgstr "Sledi povezav dokumenta/uporabnika" + +#: build/lib/core/models.py:1023 core/models.py:1023 +msgid "A link trace already exists for this document/user." +msgstr "Za ta dokument/uporabnika ลพe obstaja sled povezave." + +#: build/lib/core/models.py:1046 core/models.py:1046 +msgid "Document favorite" +msgstr "Priljubljeni dokument" + +#: build/lib/core/models.py:1047 core/models.py:1047 +msgid "Document favorites" +msgstr "Priljubljeni dokumenti" + +#: build/lib/core/models.py:1053 core/models.py:1053 +msgid "This document is already targeted by a favorite relation instance for the same user." +msgstr "Ta dokument je ลพe ciljno usmerjen s priljubljenim primerkom relacije za istega uporabnika." + +#: build/lib/core/models.py:1075 core/models.py:1075 +msgid "Document/user relation" +msgstr "Odnos dokument/uporabnik" + +#: build/lib/core/models.py:1076 core/models.py:1076 +msgid "Document/user relations" +msgstr "Odnosi dokument/uporabnik" + +#: build/lib/core/models.py:1082 core/models.py:1082 +msgid "This user is already in this document." +msgstr "Ta uporabnik je ลพe v tem dokumentu." + +#: build/lib/core/models.py:1088 core/models.py:1088 +msgid "This team is already in this document." +msgstr "Ta ekipa je ลพe v tem dokumentu." + +#: build/lib/core/models.py:1094 build/lib/core/models.py:1242 +#: core/models.py:1094 core/models.py:1242 +msgid "Either user or team must be set, not both." +msgstr "Nastaviti je treba bodisi uporabnika ali ekipo, a ne obojega." + +#: build/lib/core/models.py:1156 core/models.py:1156 +msgid "description" +msgstr "opis" + +#: build/lib/core/models.py:1157 core/models.py:1157 +msgid "code" +msgstr "koda" + +#: build/lib/core/models.py:1158 core/models.py:1158 +msgid "css" +msgstr "css" + +#: build/lib/core/models.py:1160 core/models.py:1160 +msgid "public" +msgstr "javno" + +#: build/lib/core/models.py:1162 core/models.py:1162 +msgid "Whether this template is public for anyone to use." +msgstr "Ali je ta predloga javna za uporabo." + +#: build/lib/core/models.py:1168 core/models.py:1168 +msgid "Template" +msgstr "Predloga" + +#: build/lib/core/models.py:1169 core/models.py:1169 +msgid "Templates" +msgstr "Predloge" + +#: build/lib/core/models.py:1223 core/models.py:1223 +msgid "Template/user relation" +msgstr "Odnos predloga/uporabnik" + +#: build/lib/core/models.py:1224 core/models.py:1224 +msgid "Template/user relations" +msgstr "Odnosi med predlogo in uporabnikom" + +#: build/lib/core/models.py:1230 core/models.py:1230 +msgid "This user is already in this template." +msgstr "Ta uporabnik je ลพe v tej predlogi." + +#: build/lib/core/models.py:1236 core/models.py:1236 +msgid "This team is already in this template." +msgstr "Ta ekipa je ลพe v tej predlogi." + +#: build/lib/core/models.py:1259 core/models.py:1259 +msgid "email address" +msgstr "elektronski naslov" + +#: build/lib/core/models.py:1278 core/models.py:1278 +msgid "Document invitation" +msgstr "Vabilo na dokument" + +#: build/lib/core/models.py:1279 core/models.py:1279 +msgid "Document invitations" +msgstr "Vabila na dokument" + +#: build/lib/core/models.py:1299 core/models.py:1299 +msgid "This email is already associated to a registered user." +msgstr "Ta e-poลกtni naslov je ลพe povezan z registriranim uporabnikom." + +#: core/templates/mail/html/invitation.html:162 +#: core/templates/mail/text/invitation.txt:3 +msgid "Logo email" +msgstr "E-poลกta z logotipom" + +#: core/templates/mail/html/invitation.html:209 +#: core/templates/mail/text/invitation.txt:10 +msgid "Open" +msgstr "Odpri" + +#: core/templates/mail/html/invitation.html:226 +#: core/templates/mail/text/invitation.txt:14 +msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. " +msgstr " Dokumenti, vaลกe novo bistveno orodje za organiziranje, skupno rabo in skupinsko sodelovanje pri dokumentih. " + +#: core/templates/mail/html/invitation.html:233 +#: core/templates/mail/text/invitation.txt:16 +#, python-format +msgid " Brought to you by %(brandname)s " +msgstr " Pod okriljem %(brandname)s " + diff --git a/src/backend/locale/sv_SE/LC_MESSAGES/django.po b/src/backend/locale/sv_SE/LC_MESSAGES/django.po new file mode 100644 index 0000000..7cd999d --- /dev/null +++ b/src/backend/locale/sv_SE/LC_MESSAGES/django.po @@ -0,0 +1,390 @@ +msgid "" +msgstr "" +"Project-Id-Version: lasuite-docs\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-05-22 12:09+0000\n" +"PO-Revision-Date: 2025-05-22 14:16\n" +"Last-Translator: \n" +"Language-Team: Swedish\n" +"Language: sv_SE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Crowdin-Project: lasuite-docs\n" +"X-Crowdin-Project-ID: 754523\n" +"X-Crowdin-Language: sv-SE\n" +"X-Crowdin-File: backend-conversations.pot\n" +"X-Crowdin-File-ID: 18\n" + +#: build/lib/core/admin.py:37 core/admin.py:37 +msgid "Personal info" +msgstr "Personuppgifter" + +#: build/lib/core/admin.py:50 build/lib/core/admin.py:138 core/admin.py:50 +#: core/admin.py:138 +msgid "Permissions" +msgstr "Behรถrigheter" + +#: build/lib/core/admin.py:62 core/admin.py:62 +msgid "Important dates" +msgstr "Viktiga datum" + +#: build/lib/core/admin.py:148 core/admin.py:148 +msgid "Tree structure" +msgstr "" + +#: build/lib/core/api/filters.py:47 core/api/filters.py:47 +msgid "Title" +msgstr "Titel" + +#: build/lib/core/api/filters.py:61 core/api/filters.py:61 +msgid "Creator is me" +msgstr "Skaparen รคr jag" + +#: build/lib/core/api/filters.py:64 core/api/filters.py:64 +msgid "Favorite" +msgstr "Favoriter" + +#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446 +msgid "A new document was created on your behalf!" +msgstr "Ett nytt dokument skapades รฅt dig!" + +#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450 +msgid "You have been granted ownership of a new document:" +msgstr "Du har beviljats รคganderรคtt till ett nytt dokument:" + +#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586 +msgid "Body" +msgstr "" + +#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589 +msgid "Body type" +msgstr "" + +#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595 +msgid "Format" +msgstr "Format" + +#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967 +#, python-brace-format +msgid "copy of {title}" +msgstr "" + +#: build/lib/core/enums.py:36 core/enums.py:36 +msgid "First child" +msgstr "" + +#: build/lib/core/enums.py:37 core/enums.py:37 +msgid "Last child" +msgstr "" + +#: build/lib/core/enums.py:38 core/enums.py:38 +msgid "First sibling" +msgstr "" + +#: build/lib/core/enums.py:39 core/enums.py:39 +msgid "Last sibling" +msgstr "" + +#: build/lib/core/enums.py:40 core/enums.py:40 +msgid "Left" +msgstr "" + +#: build/lib/core/enums.py:41 core/enums.py:41 +msgid "Right" +msgstr "" + +#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56 +#: core/models.py:63 +msgid "Reader" +msgstr "" + +#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57 +#: core/models.py:64 +msgid "Editor" +msgstr "" + +#: build/lib/core/models.py:65 core/models.py:65 +msgid "Administrator" +msgstr "Administratรถr" + +#: build/lib/core/models.py:66 core/models.py:66 +msgid "Owner" +msgstr "" + +#: build/lib/core/models.py:77 core/models.py:77 +msgid "Restricted" +msgstr "" + +#: build/lib/core/models.py:81 core/models.py:81 +msgid "Authenticated" +msgstr "" + +#: build/lib/core/models.py:83 core/models.py:83 +msgid "Public" +msgstr "Publik" + +#: build/lib/core/models.py:154 core/models.py:154 +msgid "id" +msgstr "" + +#: build/lib/core/models.py:155 core/models.py:155 +msgid "primary key for the record as UUID" +msgstr "" + +#: build/lib/core/models.py:161 core/models.py:161 +msgid "created on" +msgstr "" + +#: build/lib/core/models.py:162 core/models.py:162 +msgid "date and time at which a record was created" +msgstr "" + +#: build/lib/core/models.py:167 core/models.py:167 +msgid "updated on" +msgstr "" + +#: build/lib/core/models.py:168 core/models.py:168 +msgid "date and time at which a record was last updated" +msgstr "" + +#: build/lib/core/models.py:204 core/models.py:204 +msgid "We couldn't find a user with this sub but the email is already associated with a registered user." +msgstr "" + +#: build/lib/core/models.py:217 core/models.py:217 +msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters." +msgstr "" + +#: build/lib/core/models.py:223 core/models.py:223 +msgid "sub" +msgstr "" + +#: build/lib/core/models.py:225 core/models.py:225 +msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only." +msgstr "" + +#: build/lib/core/models.py:234 core/models.py:234 +msgid "full name" +msgstr "" + +#: build/lib/core/models.py:235 core/models.py:235 +msgid "short name" +msgstr "" + +#: build/lib/core/models.py:237 core/models.py:237 +msgid "identity email address" +msgstr "" + +#: build/lib/core/models.py:242 core/models.py:242 +msgid "admin email address" +msgstr "" + +#: build/lib/core/models.py:249 core/models.py:249 +msgid "language" +msgstr "" + +#: build/lib/core/models.py:250 core/models.py:250 +msgid "The language in which the user wants to see the interface." +msgstr "" + +#: build/lib/core/models.py:258 core/models.py:258 +msgid "The timezone in which the user wants to see times." +msgstr "" + +#: build/lib/core/models.py:261 core/models.py:261 +msgid "device" +msgstr "" + +#: build/lib/core/models.py:263 core/models.py:263 +msgid "Whether the user is a device or a real user." +msgstr "" + +#: build/lib/core/models.py:266 core/models.py:266 +msgid "staff status" +msgstr "" + +#: build/lib/core/models.py:268 core/models.py:268 +msgid "Whether the user can log into this admin site." +msgstr "" + +#: build/lib/core/models.py:271 core/models.py:271 +msgid "active" +msgstr "aktiv" + +#: build/lib/core/models.py:274 core/models.py:274 +msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts." +msgstr "" + +#: build/lib/core/models.py:286 core/models.py:286 +msgid "user" +msgstr "" + +#: build/lib/core/models.py:287 core/models.py:287 +msgid "users" +msgstr "" + +#: build/lib/core/models.py:470 build/lib/core/models.py:1155 +#: core/models.py:470 core/models.py:1155 +msgid "title" +msgstr "" + +#: build/lib/core/models.py:471 core/models.py:471 +msgid "excerpt" +msgstr "" + +#: build/lib/core/models.py:519 core/models.py:519 +msgid "Document" +msgstr "" + +#: build/lib/core/models.py:520 core/models.py:520 +msgid "Documents" +msgstr "" + +#: build/lib/core/models.py:532 build/lib/core/models.py:873 core/models.py:532 +#: core/models.py:873 +msgid "Untitled Document" +msgstr "" + +#: build/lib/core/models.py:908 core/models.py:908 +#, python-brace-format +msgid "{name} shared a document with you!" +msgstr "" + +#: build/lib/core/models.py:912 core/models.py:912 +#, python-brace-format +msgid "{name} invited you with the role \"{role}\" on the following document:" +msgstr "" + +#: build/lib/core/models.py:918 core/models.py:918 +#, python-brace-format +msgid "{name} shared a document with you: {title}" +msgstr "" + +#: build/lib/core/models.py:1016 core/models.py:1016 +msgid "Document/user link trace" +msgstr "" + +#: build/lib/core/models.py:1017 core/models.py:1017 +msgid "Document/user link traces" +msgstr "" + +#: build/lib/core/models.py:1023 core/models.py:1023 +msgid "A link trace already exists for this document/user." +msgstr "" + +#: build/lib/core/models.py:1046 core/models.py:1046 +msgid "Document favorite" +msgstr "" + +#: build/lib/core/models.py:1047 core/models.py:1047 +msgid "Document favorites" +msgstr "" + +#: build/lib/core/models.py:1053 core/models.py:1053 +msgid "This document is already targeted by a favorite relation instance for the same user." +msgstr "" + +#: build/lib/core/models.py:1075 core/models.py:1075 +msgid "Document/user relation" +msgstr "" + +#: build/lib/core/models.py:1076 core/models.py:1076 +msgid "Document/user relations" +msgstr "" + +#: build/lib/core/models.py:1082 core/models.py:1082 +msgid "This user is already in this document." +msgstr "" + +#: build/lib/core/models.py:1088 core/models.py:1088 +msgid "This team is already in this document." +msgstr "" + +#: build/lib/core/models.py:1094 build/lib/core/models.py:1242 +#: core/models.py:1094 core/models.py:1242 +msgid "Either user or team must be set, not both." +msgstr "" + +#: build/lib/core/models.py:1156 core/models.py:1156 +msgid "description" +msgstr "" + +#: build/lib/core/models.py:1157 core/models.py:1157 +msgid "code" +msgstr "" + +#: build/lib/core/models.py:1158 core/models.py:1158 +msgid "css" +msgstr "" + +#: build/lib/core/models.py:1160 core/models.py:1160 +msgid "public" +msgstr "" + +#: build/lib/core/models.py:1162 core/models.py:1162 +msgid "Whether this template is public for anyone to use." +msgstr "" + +#: build/lib/core/models.py:1168 core/models.py:1168 +msgid "Template" +msgstr "" + +#: build/lib/core/models.py:1169 core/models.py:1169 +msgid "Templates" +msgstr "" + +#: build/lib/core/models.py:1223 core/models.py:1223 +msgid "Template/user relation" +msgstr "" + +#: build/lib/core/models.py:1224 core/models.py:1224 +msgid "Template/user relations" +msgstr "" + +#: build/lib/core/models.py:1230 core/models.py:1230 +msgid "This user is already in this template." +msgstr "" + +#: build/lib/core/models.py:1236 core/models.py:1236 +msgid "This team is already in this template." +msgstr "" + +#: build/lib/core/models.py:1259 core/models.py:1259 +msgid "email address" +msgstr "e-postadress" + +#: build/lib/core/models.py:1278 core/models.py:1278 +msgid "Document invitation" +msgstr "Bjud in dokument" + +#: build/lib/core/models.py:1279 core/models.py:1279 +msgid "Document invitations" +msgstr "Inbjudningar dokument" + +#: build/lib/core/models.py:1299 core/models.py:1299 +msgid "This email is already associated to a registered user." +msgstr "Denna e-postadress รคr redan associerad med en registrerad anvรคndare." + +#: core/templates/mail/html/invitation.html:162 +#: core/templates/mail/text/invitation.txt:3 +msgid "Logo email" +msgstr "Logotyp e-post" + +#: core/templates/mail/html/invitation.html:209 +#: core/templates/mail/text/invitation.txt:10 +msgid "Open" +msgstr "ร–ppna" + +#: core/templates/mail/html/invitation.html:226 +#: core/templates/mail/text/invitation.txt:14 +msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. " +msgstr "" + +#: core/templates/mail/html/invitation.html:233 +#: core/templates/mail/text/invitation.txt:16 +#, python-format +msgid " Brought to you by %(brandname)s " +msgstr "" + diff --git a/src/backend/locale/tr_TR/LC_MESSAGES/django.po b/src/backend/locale/tr_TR/LC_MESSAGES/django.po new file mode 100644 index 0000000..c8fd8fd --- /dev/null +++ b/src/backend/locale/tr_TR/LC_MESSAGES/django.po @@ -0,0 +1,390 @@ +msgid "" +msgstr "" +"Project-Id-Version: lasuite-docs\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-05-22 12:09+0000\n" +"PO-Revision-Date: 2025-05-22 14:16\n" +"Last-Translator: \n" +"Language-Team: Turkish\n" +"Language: tr_TR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Crowdin-Project: lasuite-docs\n" +"X-Crowdin-Project-ID: 754523\n" +"X-Crowdin-Language: tr\n" +"X-Crowdin-File: backend-conversations.pot\n" +"X-Crowdin-File-ID: 18\n" + +#: build/lib/core/admin.py:37 core/admin.py:37 +msgid "Personal info" +msgstr "" + +#: build/lib/core/admin.py:50 build/lib/core/admin.py:138 core/admin.py:50 +#: core/admin.py:138 +msgid "Permissions" +msgstr "" + +#: build/lib/core/admin.py:62 core/admin.py:62 +msgid "Important dates" +msgstr "" + +#: build/lib/core/admin.py:148 core/admin.py:148 +msgid "Tree structure" +msgstr "" + +#: build/lib/core/api/filters.py:47 core/api/filters.py:47 +msgid "Title" +msgstr "" + +#: build/lib/core/api/filters.py:61 core/api/filters.py:61 +msgid "Creator is me" +msgstr "" + +#: build/lib/core/api/filters.py:64 core/api/filters.py:64 +msgid "Favorite" +msgstr "" + +#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446 +msgid "A new document was created on your behalf!" +msgstr "" + +#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450 +msgid "You have been granted ownership of a new document:" +msgstr "" + +#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586 +msgid "Body" +msgstr "" + +#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589 +msgid "Body type" +msgstr "" + +#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595 +msgid "Format" +msgstr "" + +#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967 +#, python-brace-format +msgid "copy of {title}" +msgstr "" + +#: build/lib/core/enums.py:36 core/enums.py:36 +msgid "First child" +msgstr "" + +#: build/lib/core/enums.py:37 core/enums.py:37 +msgid "Last child" +msgstr "" + +#: build/lib/core/enums.py:38 core/enums.py:38 +msgid "First sibling" +msgstr "" + +#: build/lib/core/enums.py:39 core/enums.py:39 +msgid "Last sibling" +msgstr "" + +#: build/lib/core/enums.py:40 core/enums.py:40 +msgid "Left" +msgstr "" + +#: build/lib/core/enums.py:41 core/enums.py:41 +msgid "Right" +msgstr "" + +#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56 +#: core/models.py:63 +msgid "Reader" +msgstr "" + +#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57 +#: core/models.py:64 +msgid "Editor" +msgstr "" + +#: build/lib/core/models.py:65 core/models.py:65 +msgid "Administrator" +msgstr "" + +#: build/lib/core/models.py:66 core/models.py:66 +msgid "Owner" +msgstr "" + +#: build/lib/core/models.py:77 core/models.py:77 +msgid "Restricted" +msgstr "" + +#: build/lib/core/models.py:81 core/models.py:81 +msgid "Authenticated" +msgstr "" + +#: build/lib/core/models.py:83 core/models.py:83 +msgid "Public" +msgstr "" + +#: build/lib/core/models.py:154 core/models.py:154 +msgid "id" +msgstr "" + +#: build/lib/core/models.py:155 core/models.py:155 +msgid "primary key for the record as UUID" +msgstr "" + +#: build/lib/core/models.py:161 core/models.py:161 +msgid "created on" +msgstr "" + +#: build/lib/core/models.py:162 core/models.py:162 +msgid "date and time at which a record was created" +msgstr "" + +#: build/lib/core/models.py:167 core/models.py:167 +msgid "updated on" +msgstr "" + +#: build/lib/core/models.py:168 core/models.py:168 +msgid "date and time at which a record was last updated" +msgstr "" + +#: build/lib/core/models.py:204 core/models.py:204 +msgid "We couldn't find a user with this sub but the email is already associated with a registered user." +msgstr "" + +#: build/lib/core/models.py:217 core/models.py:217 +msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters." +msgstr "" + +#: build/lib/core/models.py:223 core/models.py:223 +msgid "sub" +msgstr "" + +#: build/lib/core/models.py:225 core/models.py:225 +msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only." +msgstr "" + +#: build/lib/core/models.py:234 core/models.py:234 +msgid "full name" +msgstr "" + +#: build/lib/core/models.py:235 core/models.py:235 +msgid "short name" +msgstr "" + +#: build/lib/core/models.py:237 core/models.py:237 +msgid "identity email address" +msgstr "" + +#: build/lib/core/models.py:242 core/models.py:242 +msgid "admin email address" +msgstr "" + +#: build/lib/core/models.py:249 core/models.py:249 +msgid "language" +msgstr "" + +#: build/lib/core/models.py:250 core/models.py:250 +msgid "The language in which the user wants to see the interface." +msgstr "" + +#: build/lib/core/models.py:258 core/models.py:258 +msgid "The timezone in which the user wants to see times." +msgstr "" + +#: build/lib/core/models.py:261 core/models.py:261 +msgid "device" +msgstr "" + +#: build/lib/core/models.py:263 core/models.py:263 +msgid "Whether the user is a device or a real user." +msgstr "" + +#: build/lib/core/models.py:266 core/models.py:266 +msgid "staff status" +msgstr "" + +#: build/lib/core/models.py:268 core/models.py:268 +msgid "Whether the user can log into this admin site." +msgstr "" + +#: build/lib/core/models.py:271 core/models.py:271 +msgid "active" +msgstr "" + +#: build/lib/core/models.py:274 core/models.py:274 +msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts." +msgstr "" + +#: build/lib/core/models.py:286 core/models.py:286 +msgid "user" +msgstr "" + +#: build/lib/core/models.py:287 core/models.py:287 +msgid "users" +msgstr "" + +#: build/lib/core/models.py:470 build/lib/core/models.py:1155 +#: core/models.py:470 core/models.py:1155 +msgid "title" +msgstr "" + +#: build/lib/core/models.py:471 core/models.py:471 +msgid "excerpt" +msgstr "" + +#: build/lib/core/models.py:519 core/models.py:519 +msgid "Document" +msgstr "" + +#: build/lib/core/models.py:520 core/models.py:520 +msgid "Documents" +msgstr "" + +#: build/lib/core/models.py:532 build/lib/core/models.py:873 core/models.py:532 +#: core/models.py:873 +msgid "Untitled Document" +msgstr "" + +#: build/lib/core/models.py:908 core/models.py:908 +#, python-brace-format +msgid "{name} shared a document with you!" +msgstr "" + +#: build/lib/core/models.py:912 core/models.py:912 +#, python-brace-format +msgid "{name} invited you with the role \"{role}\" on the following document:" +msgstr "" + +#: build/lib/core/models.py:918 core/models.py:918 +#, python-brace-format +msgid "{name} shared a document with you: {title}" +msgstr "" + +#: build/lib/core/models.py:1016 core/models.py:1016 +msgid "Document/user link trace" +msgstr "" + +#: build/lib/core/models.py:1017 core/models.py:1017 +msgid "Document/user link traces" +msgstr "" + +#: build/lib/core/models.py:1023 core/models.py:1023 +msgid "A link trace already exists for this document/user." +msgstr "" + +#: build/lib/core/models.py:1046 core/models.py:1046 +msgid "Document favorite" +msgstr "" + +#: build/lib/core/models.py:1047 core/models.py:1047 +msgid "Document favorites" +msgstr "" + +#: build/lib/core/models.py:1053 core/models.py:1053 +msgid "This document is already targeted by a favorite relation instance for the same user." +msgstr "" + +#: build/lib/core/models.py:1075 core/models.py:1075 +msgid "Document/user relation" +msgstr "" + +#: build/lib/core/models.py:1076 core/models.py:1076 +msgid "Document/user relations" +msgstr "" + +#: build/lib/core/models.py:1082 core/models.py:1082 +msgid "This user is already in this document." +msgstr "" + +#: build/lib/core/models.py:1088 core/models.py:1088 +msgid "This team is already in this document." +msgstr "" + +#: build/lib/core/models.py:1094 build/lib/core/models.py:1242 +#: core/models.py:1094 core/models.py:1242 +msgid "Either user or team must be set, not both." +msgstr "" + +#: build/lib/core/models.py:1156 core/models.py:1156 +msgid "description" +msgstr "" + +#: build/lib/core/models.py:1157 core/models.py:1157 +msgid "code" +msgstr "" + +#: build/lib/core/models.py:1158 core/models.py:1158 +msgid "css" +msgstr "" + +#: build/lib/core/models.py:1160 core/models.py:1160 +msgid "public" +msgstr "" + +#: build/lib/core/models.py:1162 core/models.py:1162 +msgid "Whether this template is public for anyone to use." +msgstr "" + +#: build/lib/core/models.py:1168 core/models.py:1168 +msgid "Template" +msgstr "" + +#: build/lib/core/models.py:1169 core/models.py:1169 +msgid "Templates" +msgstr "" + +#: build/lib/core/models.py:1223 core/models.py:1223 +msgid "Template/user relation" +msgstr "" + +#: build/lib/core/models.py:1224 core/models.py:1224 +msgid "Template/user relations" +msgstr "" + +#: build/lib/core/models.py:1230 core/models.py:1230 +msgid "This user is already in this template." +msgstr "" + +#: build/lib/core/models.py:1236 core/models.py:1236 +msgid "This team is already in this template." +msgstr "" + +#: build/lib/core/models.py:1259 core/models.py:1259 +msgid "email address" +msgstr "" + +#: build/lib/core/models.py:1278 core/models.py:1278 +msgid "Document invitation" +msgstr "" + +#: build/lib/core/models.py:1279 core/models.py:1279 +msgid "Document invitations" +msgstr "" + +#: build/lib/core/models.py:1299 core/models.py:1299 +msgid "This email is already associated to a registered user." +msgstr "" + +#: core/templates/mail/html/invitation.html:162 +#: core/templates/mail/text/invitation.txt:3 +msgid "Logo email" +msgstr "" + +#: core/templates/mail/html/invitation.html:209 +#: core/templates/mail/text/invitation.txt:10 +msgid "Open" +msgstr "" + +#: core/templates/mail/html/invitation.html:226 +#: core/templates/mail/text/invitation.txt:14 +msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. " +msgstr "" + +#: core/templates/mail/html/invitation.html:233 +#: core/templates/mail/text/invitation.txt:16 +#, python-format +msgid " Brought to you by %(brandname)s " +msgstr "" + diff --git a/src/backend/locale/zh_CN/LC_MESSAGES/django.po b/src/backend/locale/zh_CN/LC_MESSAGES/django.po new file mode 100644 index 0000000..ceaa0cd --- /dev/null +++ b/src/backend/locale/zh_CN/LC_MESSAGES/django.po @@ -0,0 +1,390 @@ +msgid "" +msgstr "" +"Project-Id-Version: lasuite-docs\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-05-22 12:09+0000\n" +"PO-Revision-Date: 2025-05-22 14:16\n" +"Last-Translator: \n" +"Language-Team: Chinese Simplified\n" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Crowdin-Project: lasuite-docs\n" +"X-Crowdin-Project-ID: 754523\n" +"X-Crowdin-Language: zh-CN\n" +"X-Crowdin-File: backend-conversations.pot\n" +"X-Crowdin-File-ID: 18\n" + +#: build/lib/core/admin.py:37 core/admin.py:37 +msgid "Personal info" +msgstr "ไธชไบบไฟกๆฏ" + +#: build/lib/core/admin.py:50 build/lib/core/admin.py:138 core/admin.py:50 +#: core/admin.py:138 +msgid "Permissions" +msgstr "ๆƒ้™" + +#: build/lib/core/admin.py:62 core/admin.py:62 +msgid "Important dates" +msgstr "้‡่ฆๆ—ฅๆœŸ" + +#: build/lib/core/admin.py:148 core/admin.py:148 +msgid "Tree structure" +msgstr "ๆ ‘็Šถ็ป“ๆž„" + +#: build/lib/core/api/filters.py:47 core/api/filters.py:47 +msgid "Title" +msgstr "ๆ ‡้ข˜" + +#: build/lib/core/api/filters.py:61 core/api/filters.py:61 +msgid "Creator is me" +msgstr "ๅˆ›ๅปบ่€…ๆ˜ฏๆˆ‘" + +#: build/lib/core/api/filters.py:64 core/api/filters.py:64 +msgid "Favorite" +msgstr "ๆ”ถ่—" + +#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446 +msgid "A new document was created on your behalf!" +msgstr "ๅทฒไธบๆ‚จๅˆ›ๅปบไบ†ไธ€ไปฝๆ–ฐๆ–‡ๆกฃ๏ผ" + +#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450 +msgid "You have been granted ownership of a new document:" +msgstr "ๆ‚จๅทฒ่ขซๆŽˆไบˆๆ–ฐๆ–‡ๆกฃ็š„ๆ‰€ๆœ‰ๆƒ๏ผš" + +#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586 +msgid "Body" +msgstr "ๆญฃๆ–‡" + +#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589 +msgid "Body type" +msgstr "ๆญฃๆ–‡็ฑปๅž‹" + +#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595 +msgid "Format" +msgstr "ๆ ผๅผ" + +#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967 +#, python-brace-format +msgid "copy of {title}" +msgstr "{title} ็š„ๅ‰ฏๆœฌ" + +#: build/lib/core/enums.py:36 core/enums.py:36 +msgid "First child" +msgstr "็ฌฌไธ€ไธชๅญ้กน" + +#: build/lib/core/enums.py:37 core/enums.py:37 +msgid "Last child" +msgstr "ๆœ€ๅŽไธ€ไธชๅญ้กน" + +#: build/lib/core/enums.py:38 core/enums.py:38 +msgid "First sibling" +msgstr "็ฌฌไธ€ไธชๅŒ็บง้กน" + +#: build/lib/core/enums.py:39 core/enums.py:39 +msgid "Last sibling" +msgstr "ๆœ€ๅŽไธ€ไธชๅŒ็บง้กน" + +#: build/lib/core/enums.py:40 core/enums.py:40 +msgid "Left" +msgstr "ๅทฆ" + +#: build/lib/core/enums.py:41 core/enums.py:41 +msgid "Right" +msgstr "ๅณ" + +#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56 +#: core/models.py:63 +msgid "Reader" +msgstr "้˜…่ฏป่€…" + +#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57 +#: core/models.py:64 +msgid "Editor" +msgstr "็ผ–่พ‘่€…" + +#: build/lib/core/models.py:65 core/models.py:65 +msgid "Administrator" +msgstr "่ถ…็บง็ฎก็†ๅ‘˜" + +#: build/lib/core/models.py:66 core/models.py:66 +msgid "Owner" +msgstr "ๆ‰€ๆœ‰่€…" + +#: build/lib/core/models.py:77 core/models.py:77 +msgid "Restricted" +msgstr "ๅ—้™็š„" + +#: build/lib/core/models.py:81 core/models.py:81 +msgid "Authenticated" +msgstr "ๅทฒ้ชŒ่ฏ" + +#: build/lib/core/models.py:83 core/models.py:83 +msgid "Public" +msgstr "ๅ…ฌๅผ€" + +#: build/lib/core/models.py:154 core/models.py:154 +msgid "id" +msgstr "id" + +#: build/lib/core/models.py:155 core/models.py:155 +msgid "primary key for the record as UUID" +msgstr "่ฎฐๅฝ•็š„ไธปๅฏ†้’ฅไธบ UUID" + +#: build/lib/core/models.py:161 core/models.py:161 +msgid "created on" +msgstr "ๅˆ›ๅปบๆ—ถ้—ด" + +#: build/lib/core/models.py:162 core/models.py:162 +msgid "date and time at which a record was created" +msgstr "่ฎฐๅฝ•็š„ๅˆ›ๅปบๆ—ฅๆœŸๅ’Œๆ—ถ้—ด" + +#: build/lib/core/models.py:167 core/models.py:167 +msgid "updated on" +msgstr "ๆ›ดๆ–ฐๆ—ถ้—ด" + +#: build/lib/core/models.py:168 core/models.py:168 +msgid "date and time at which a record was last updated" +msgstr "่ฎฐๅฝ•็š„ๆœ€ๅŽๆ›ดๆ–ฐๆ—ถ้—ด" + +#: build/lib/core/models.py:204 core/models.py:204 +msgid "We couldn't find a user with this sub but the email is already associated with a registered user." +msgstr "ๆœชๆ‰พๅˆฐๅ…ทๆœ‰่ฏฅ sub ็š„็”จๆˆท๏ผŒไฝ†่ฏฅ้‚ฎ็ฎฑๅทฒๅ…ณ่”ๅˆฐไธ€ไธชๆณจๅ†Œ็”จๆˆทใ€‚" + +#: build/lib/core/models.py:217 core/models.py:217 +msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters." +msgstr "่ฏท่พ“ๅ…ฅๆœ‰ๆ•ˆ็š„ subใ€‚่ฏฅๅ€ผๅช่ƒฝๅŒ…ๅซๅญ—ๆฏใ€ๆ•ฐๅญ—ๅŠ @/./+/-/_/: ๅญ—็ฌฆใ€‚" + +#: build/lib/core/models.py:223 core/models.py:223 +msgid "sub" +msgstr "sub" + +#: build/lib/core/models.py:225 core/models.py:225 +msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only." +msgstr "ๅฟ…ๅกซใ€‚ๆœ€ๅคš 255 ไธชๅญ—็ฌฆ๏ผŒไป…ๅ…่ฎธๅญ—ๆฏใ€ๆ•ฐๅญ—ๅŠ @/./+/-/_/: ๅญ—็ฌฆใ€‚" + +#: build/lib/core/models.py:234 core/models.py:234 +msgid "full name" +msgstr "ๅ…จๅ" + +#: build/lib/core/models.py:235 core/models.py:235 +msgid "short name" +msgstr "็ฎ€็งฐ" + +#: build/lib/core/models.py:237 core/models.py:237 +msgid "identity email address" +msgstr "่บซไปฝ็”ตๅญ้‚ฎไปถๅœฐๅ€" + +#: build/lib/core/models.py:242 core/models.py:242 +msgid "admin email address" +msgstr "็ฎก็†ๅ‘˜็”ตๅญ้‚ฎไปถๅœฐๅ€" + +#: build/lib/core/models.py:249 core/models.py:249 +msgid "language" +msgstr "่ฏญ่จ€" + +#: build/lib/core/models.py:250 core/models.py:250 +msgid "The language in which the user wants to see the interface." +msgstr "็”จๆˆทๅธŒๆœ›็œ‹ๅˆฐ็š„็•Œ้ข่ฏญ่จ€ใ€‚" + +#: build/lib/core/models.py:258 core/models.py:258 +msgid "The timezone in which the user wants to see times." +msgstr "็”จๆˆทๆŸฅ็œ‹ๆ—ถ้—ดๅธŒๆœ›็š„ๆ—ถๅŒบใ€‚" + +#: build/lib/core/models.py:261 core/models.py:261 +msgid "device" +msgstr "่ฎพๅค‡" + +#: build/lib/core/models.py:263 core/models.py:263 +msgid "Whether the user is a device or a real user." +msgstr "็”จๆˆทๆ˜ฏ่ฎพๅค‡่ฟ˜ๆ˜ฏ็œŸๅฎž็”จๆˆทใ€‚" + +#: build/lib/core/models.py:266 core/models.py:266 +msgid "staff status" +msgstr "ๅ‘˜ๅทฅ็Šถๆ€" + +#: build/lib/core/models.py:268 core/models.py:268 +msgid "Whether the user can log into this admin site." +msgstr "็”จๆˆทๆ˜ฏๅฆๅฏไปฅ็™ปๅฝ•่ฏฅ็ฎก็†ๅ‘˜็ซ™็‚นใ€‚" + +#: build/lib/core/models.py:271 core/models.py:271 +msgid "active" +msgstr "ๆฟ€ๆดป" + +#: build/lib/core/models.py:274 core/models.py:274 +msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts." +msgstr "ๆ˜ฏๅฆๅบ”ๅฐ†ๆญค็”จๆˆท่ง†ไธบๆดป่ทƒ็”จๆˆทใ€‚ๅ–ๆถˆ้€‰ๆ‹ฉๆญค้€‰้กน่€Œไธๆ˜ฏๅˆ ้™ค่ดฆๆˆทใ€‚" + +#: build/lib/core/models.py:286 core/models.py:286 +msgid "user" +msgstr "็”จๆˆท" + +#: build/lib/core/models.py:287 core/models.py:287 +msgid "users" +msgstr "ไธช็”จๆˆท" + +#: build/lib/core/models.py:470 build/lib/core/models.py:1155 +#: core/models.py:470 core/models.py:1155 +msgid "title" +msgstr "ๆ ‡้ข˜" + +#: build/lib/core/models.py:471 core/models.py:471 +msgid "excerpt" +msgstr "ๆ‘˜่ฆ" + +#: build/lib/core/models.py:519 core/models.py:519 +msgid "Document" +msgstr "ๆ–‡ๆกฃ" + +#: build/lib/core/models.py:520 core/models.py:520 +msgid "Documents" +msgstr "ไธชๆ–‡ๆกฃ" + +#: build/lib/core/models.py:532 build/lib/core/models.py:873 core/models.py:532 +#: core/models.py:873 +msgid "Untitled Document" +msgstr "ๆœชๅ‘ฝๅๆ–‡ๆกฃ" + +#: build/lib/core/models.py:908 core/models.py:908 +#, python-brace-format +msgid "{name} shared a document with you!" +msgstr "{name} ไธŽๆ‚จๅ…ฑไบซไบ†ไธ€ไธชๆ–‡ๆกฃ๏ผ" + +#: build/lib/core/models.py:912 core/models.py:912 +#, python-brace-format +msgid "{name} invited you with the role \"{role}\" on the following document:" +msgstr "{name} ้‚€่ฏทๆ‚จไปฅโ€œ{role}โ€่ง’่‰ฒ่ฎฟ้—ฎไปฅไธ‹ๆ–‡ๆกฃ๏ผš" + +#: build/lib/core/models.py:918 core/models.py:918 +#, python-brace-format +msgid "{name} shared a document with you: {title}" +msgstr "{name} ไธŽๆ‚จๅ…ฑไบซไบ†ไธ€ไธชๆ–‡ๆกฃ๏ผš{title}" + +#: build/lib/core/models.py:1016 core/models.py:1016 +msgid "Document/user link trace" +msgstr "ๆ–‡ๆกฃ/็”จๆˆท้“พๆŽฅ่ทŸ่ธช" + +#: build/lib/core/models.py:1017 core/models.py:1017 +msgid "Document/user link traces" +msgstr "ไธชๆ–‡ๆกฃ/็”จๆˆท้“พๆŽฅ่ทŸ่ธช" + +#: build/lib/core/models.py:1023 core/models.py:1023 +msgid "A link trace already exists for this document/user." +msgstr "ๆญคๆ–‡ๆกฃ/็”จๆˆท็š„้“พๆŽฅ่ทŸ่ธชๅทฒๅญ˜ๅœจใ€‚" + +#: build/lib/core/models.py:1046 core/models.py:1046 +msgid "Document favorite" +msgstr "ๆ–‡ๆกฃๆ”ถ่—" + +#: build/lib/core/models.py:1047 core/models.py:1047 +msgid "Document favorites" +msgstr "ๆ–‡ๆกฃๆ”ถ่—ๅคน" + +#: build/lib/core/models.py:1053 core/models.py:1053 +msgid "This document is already targeted by a favorite relation instance for the same user." +msgstr "่ฏฅๆ–‡ๆกฃๅทฒ่ขซๅŒไธ€็”จๆˆท็š„ๆ”ถ่—ๅ…ณ็ณปๅฎžไพ‹ๅ…ณ่”ใ€‚" + +#: build/lib/core/models.py:1075 core/models.py:1075 +msgid "Document/user relation" +msgstr "ๆ–‡ๆกฃ/็”จๆˆทๅ…ณ็ณป" + +#: build/lib/core/models.py:1076 core/models.py:1076 +msgid "Document/user relations" +msgstr "ๆ–‡ๆกฃ/็”จๆˆทๅ…ณ็ณป้›†" + +#: build/lib/core/models.py:1082 core/models.py:1082 +msgid "This user is already in this document." +msgstr "่ฏฅ็”จๆˆทๅทฒๅœจๆญคๆ–‡ๆกฃไธญใ€‚" + +#: build/lib/core/models.py:1088 core/models.py:1088 +msgid "This team is already in this document." +msgstr "่ฏฅๅ›ข้˜Ÿๅทฒๅœจๆญคๆ–‡ๆกฃไธญใ€‚" + +#: build/lib/core/models.py:1094 build/lib/core/models.py:1242 +#: core/models.py:1094 core/models.py:1242 +msgid "Either user or team must be set, not both." +msgstr "ๅฟ…้กป่ฎพ็ฝฎ็”จๆˆทๆˆ–ๅ›ข้˜Ÿไน‹ไธ€๏ผŒไธ่ƒฝๅŒๆ—ถ่ฎพ็ฝฎไธค่€…ใ€‚" + +#: build/lib/core/models.py:1156 core/models.py:1156 +msgid "description" +msgstr "่ฏดๆ˜Ž" + +#: build/lib/core/models.py:1157 core/models.py:1157 +msgid "code" +msgstr "ไปฃ็ " + +#: build/lib/core/models.py:1158 core/models.py:1158 +msgid "css" +msgstr "css" + +#: build/lib/core/models.py:1160 core/models.py:1160 +msgid "public" +msgstr "ๅ…ฌๅผ€" + +#: build/lib/core/models.py:1162 core/models.py:1162 +msgid "Whether this template is public for anyone to use." +msgstr "่ฏฅๆจกๆฟๆ˜ฏๅฆๅ…ฌๅผ€ไพ›ไปปไฝ•ไบบไฝฟ็”จใ€‚" + +#: build/lib/core/models.py:1168 core/models.py:1168 +msgid "Template" +msgstr "ๆจกๆฟ" + +#: build/lib/core/models.py:1169 core/models.py:1169 +msgid "Templates" +msgstr "ๆจกๆฟ" + +#: build/lib/core/models.py:1223 core/models.py:1223 +msgid "Template/user relation" +msgstr "ๆจกๆฟ/็”จๆˆทๅ…ณ็ณป" + +#: build/lib/core/models.py:1224 core/models.py:1224 +msgid "Template/user relations" +msgstr "ๆจกๆฟ/็”จๆˆทๅ…ณ็ณป้›†" + +#: build/lib/core/models.py:1230 core/models.py:1230 +msgid "This user is already in this template." +msgstr "่ฏฅ็”จๆˆทๅทฒๅœจๆญคๆจกๆฟไธญใ€‚" + +#: build/lib/core/models.py:1236 core/models.py:1236 +msgid "This team is already in this template." +msgstr "่ฏฅๅ›ข้˜Ÿๅทฒๅœจๆญคๆจกๆฟไธญใ€‚" + +#: build/lib/core/models.py:1259 core/models.py:1259 +msgid "email address" +msgstr "็”ตๅญ้‚ฎไปถๅœฐๅ€" + +#: build/lib/core/models.py:1278 core/models.py:1278 +msgid "Document invitation" +msgstr "ๆ–‡ๆกฃ้‚€่ฏท" + +#: build/lib/core/models.py:1279 core/models.py:1279 +msgid "Document invitations" +msgstr "ๆ–‡ๆกฃ้‚€่ฏท" + +#: build/lib/core/models.py:1299 core/models.py:1299 +msgid "This email is already associated to a registered user." +msgstr "ๆญค็”ตๅญ้‚ฎไปถๅทฒ็ปไธŽ็Žฐๆœ‰ๆณจๅ†Œ็”จๆˆทๅ…ณ่”ใ€‚" + +#: core/templates/mail/html/invitation.html:162 +#: core/templates/mail/text/invitation.txt:3 +msgid "Logo email" +msgstr "ๅพฝๆ ‡้‚ฎไปถ" + +#: core/templates/mail/html/invitation.html:209 +#: core/templates/mail/text/invitation.txt:10 +msgid "Open" +msgstr "ๆ‰“ๅผ€" + +#: core/templates/mail/html/invitation.html:226 +#: core/templates/mail/text/invitation.txt:14 +msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. " +msgstr " Docsโ€”โ€”ๆ‚จ็š„ๅ…จๆ–ฐๅฟ…ๅค‡ๅทฅๅ…ท๏ผŒๅธฎๅŠฉๅ›ข้˜Ÿ็ป„็ป‡ใ€ๅ…ฑไบซๅ’Œๅไฝœๅค„็†ๆ–‡ๆกฃใ€‚ " + +#: core/templates/mail/html/invitation.html:233 +#: core/templates/mail/text/invitation.txt:16 +#, python-format +msgid " Brought to you by %(brandname)s " +msgstr " ็”ฑ %(brandname)s ๅ€พๅŠ›ๆ‰“้€ ใ€‚ " + diff --git a/src/backend/manage.py b/src/backend/manage.py new file mode 100644 index 0000000..2dbeb89 --- /dev/null +++ b/src/backend/manage.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +""" +conversations's sandbox management script. +""" + +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "conversations.settings") + os.environ.setdefault("DJANGO_CONFIGURATION", "Development") + + from configurations.management import execute_from_command_line + + execute_from_command_line(sys.argv) diff --git a/src/backend/pyproject.toml b/src/backend/pyproject.toml new file mode 100644 index 0000000..63bae3f --- /dev/null +++ b/src/backend/pyproject.toml @@ -0,0 +1,149 @@ +# +# conversations package +# +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[project] +name = "conversations" +version = "0.0.1" +authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Framework :: Django", + "Framework :: Django :: 5", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Natural Language :: English", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.12", +] +description = "An application to chat with your own AI." +keywords = ["Django", "AI", "Chatbot", "OpenAI", "Conversations"] +license = { file = "LICENSE" } +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "openai-agents==0.0.19", + "mlflow", + "deprecated", + "beautifulsoup4==4.13.4", + "boto3==1.38.0", + "Brotli==1.1.0", + "django-configurations==2.5.1", + "django-cors-headers==4.7.0", + "django-countries==7.6.1", + "django-filter==25.1", + "django-lasuite[all]==0.0.9", + "django-parler==2.3", + "django-redis==5.4.0", + "django-storages[s3]==1.14.6", + "django-timezone-field>=5.1", + "django==5.2.3", + "djangorestframework==3.16.0", + "drf_spectacular==0.28.0", + "dockerflow==2024.4.2", + "easy_thumbnails==2.10", + "factory_boy==3.3.3", + "gunicorn==23.0.0", + "jsonschema==4.24.0", + "lxml==5.4.0", + "markdown==3.8", + "mozilla-django-oidc==4.0.1", + "nested-multipart-parser==1.5.0", + "openai==1.87.0", + "pydantic==2.10.2", + "psycopg[binary]==3.2.9", + "PyJWT==2.10.1", + "python-magic==0.4.27", + "redis<6.0.0", + "requests==2.32.4", + "sentry-sdk==2.30.0", + "whitenoise==6.9.0", +] + +[project.urls] +"Bug Tracker" = "https://github.com/numerique-gouv/conversations/issues/new" +"Changelog" = "https://github.com/numerique-gouv/conversations/blob/main/CHANGELOG.md" +"Homepage" = "https://github.com/numerique-gouv/conversations" +"Repository" = "https://github.com/numerique-gouv/conversations" + +[project.optional-dependencies] +dev = [ + "django-extensions==4.1", + "django-test-migrations==1.5.0", + "drf-spectacular-sidecar==2025.6.1", + "freezegun==1.5.2", + "ipdb==0.13.13", + "ipython==9.3.0", + "pyfakefs==5.8.0", + "pylint-django==2.6.1", + "pylint==3.3.7", + "pytest-cov==6.2.1", + "pytest-django==4.11.1", + "pytest==8.4.0", + "pytest-icdiff==0.9", + "pytest-xdist==3.7.0", + "responses==0.25.7", + "ruff==0.11.13", + "types-requests==2.32.4.20250611", +] + +[tool.setuptools] +packages = { find = { where = ["."], exclude = ["tests"] } } +zip-safe = true + +[tool.distutils.bdist_wheel] +universal = true + +[tool.ruff] +exclude = [ + ".git", + ".venv", + "build", + "venv", + "__pycache__", +] +line-length = 100 + + +[tool.ruff.lint] +ignore = ["DJ001", "PLR2004"] +select = [ + "B", # flake8-bugbear + "BLE", # flake8-blind-except + "C4", # flake8-comprehensions + "DJ", # flake8-django + "I", # isort + "PLC", # pylint-convention + "PLE", # pylint-error + "PLR", # pylint-refactoring + "PLW", # pylint-warning + "RUF100", # Ruff unused-noqa + "RUF200", # Ruff check pyproject.toml + "S", # flake8-bandit + "SLF", # flake8-self + "T20", # flake8-print +] + +[tool.ruff.lint.isort] +section-order = ["future","standard-library","django","third-party","conversations","first-party","local-folder"] +sections = { conversations=["core"], django=["django"] } +extra-standard-library = ["tomllib"] + +[tool.ruff.lint.per-file-ignores] +"**/tests/*" = ["S", "SLF"] + +[tool.pytest.ini_options] +addopts = [ + "-v", + "--cov-report", + "term-missing", + # Allow test files to have the same name in different directories. + "--import-mode=importlib", +] +python_files = [ + "test_*.py", + "tests.py", +] diff --git a/src/backend/setup.py b/src/backend/setup.py new file mode 100644 index 0000000..afa8893 --- /dev/null +++ b/src/backend/setup.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python +"""Setup file for the conversations module. All configuration stands in the setup.cfg file.""" +# coding: utf-8 + +from setuptools import setup + +setup() diff --git a/src/frontend/.prettierrc.js b/src/frontend/.prettierrc.js new file mode 100644 index 0000000..a4db989 --- /dev/null +++ b/src/frontend/.prettierrc.js @@ -0,0 +1,7 @@ +module.exports = { + semi: true, + trailingComma: 'all', + singleQuote: true, + printWidth: 80, + tabWidth: 2, +}; diff --git a/src/frontend/Dockerfile b/src/frontend/Dockerfile new file mode 100644 index 0000000..0a9fb20 --- /dev/null +++ b/src/frontend/Dockerfile @@ -0,0 +1,68 @@ +FROM node:24-alpine AS frontend-deps + +# Upgrade system packages to install security updates +RUN apk update && \ + apk upgrade && \ + rm -rf /var/cache/apk/* + +WORKDIR /home/frontend/ + +COPY ./src/frontend/package.json ./package.json +COPY ./src/frontend/yarn.lock ./yarn.lock +COPY ./src/frontend/apps/conversations/package.json ./apps/conversations/package.json +COPY ./src/frontend/packages/eslint-config-conversations/package.json ./packages/eslint-config-conversations/package.json + +RUN yarn install --frozen-lockfile + +COPY .dockerignore ./.dockerignore +COPY ./src/frontend/.prettierrc.js ./.prettierrc.js +COPY ./src/frontend/packages/eslint-config-conversations ./packages/eslint-config-conversations +COPY ./src/frontend/apps/conversations ./apps/conversations + +### ---- Front-end builder image ---- +FROM frontend-deps AS conversations + +WORKDIR /home/frontend/apps/conversations + +FROM frontend-deps AS conversations-dev + +WORKDIR /home/frontend/apps/conversations + +EXPOSE 3000 + +CMD [ "yarn", "dev"] + +# Tilt will rebuild conversations target so, we dissociate conversations and conversations-builder +# to avoid rebuilding the app at every changes. +FROM conversations AS conversations-builder + +WORKDIR /home/frontend/apps/conversations + +ARG API_ORIGIN +ENV NEXT_PUBLIC_API_ORIGIN=${API_ORIGIN} + +ARG SW_DEACTIVATED +ENV NEXT_PUBLIC_SW_DEACTIVATED=${SW_DEACTIVATED} + +ARG PRODUCT_NAME +ENV NEXT_PUBLIC_PRODUCT_NAME=${PRODUCT_NAME} + +RUN yarn build + +# ---- Front-end image ---- +FROM nginxinc/nginx-unprivileged:alpine3.21 AS frontend-production + +# Un-privileged user running the application +ARG DOCKER_USER +USER ${DOCKER_USER} + +COPY --from=conversations-builder \ + /home/frontend/apps/conversations/out \ + /usr/share/nginx/html + +COPY ./src/frontend/apps/conversations/conf/default.conf /etc/nginx/conf.d +COPY ./docker/files/usr/local/bin/entrypoint /usr/local/bin/entrypoint + +ENTRYPOINT [ "/usr/local/bin/entrypoint" ] + +CMD ["nginx", "-g", "daemon off;"] diff --git a/src/frontend/apps/conversations/.env b/src/frontend/apps/conversations/.env new file mode 100644 index 0000000..f89a6e6 --- /dev/null +++ b/src/frontend/apps/conversations/.env @@ -0,0 +1,2 @@ +NEXT_PUBLIC_API_ORIGIN= +NEXT_PUBLIC_PRODUCT_NAME= diff --git a/src/frontend/apps/conversations/.env.development b/src/frontend/apps/conversations/.env.development new file mode 100644 index 0000000..99a452e --- /dev/null +++ b/src/frontend/apps/conversations/.env.development @@ -0,0 +1,2 @@ +NEXT_PUBLIC_API_ORIGIN=http://localhost:8071 +NEXT_PUBLIC_PRODUCT_NAME= diff --git a/src/frontend/apps/conversations/.env.test b/src/frontend/apps/conversations/.env.test new file mode 100644 index 0000000..9a4d514 --- /dev/null +++ b/src/frontend/apps/conversations/.env.test @@ -0,0 +1 @@ +NEXT_PUBLIC_API_ORIGIN=http://test.jest diff --git a/src/frontend/apps/conversations/.eslintrc.js b/src/frontend/apps/conversations/.eslintrc.js new file mode 100644 index 0000000..276e98b --- /dev/null +++ b/src/frontend/apps/conversations/.eslintrc.js @@ -0,0 +1,14 @@ +module.exports = { + root: true, + extends: ['conversations/next'], + parserOptions: { + tsconfigRootDir: __dirname, + project: ['./tsconfig.json'], + }, + settings: { + next: { + rootDir: __dirname, + }, + }, + ignorePatterns: ['node_modules', '.eslintrc.js', 'service-worker.js'], +}; diff --git a/src/frontend/apps/conversations/.gitignore b/src/frontend/apps/conversations/.gitignore new file mode 100644 index 0000000..0abcbb5 --- /dev/null +++ b/src/frontend/apps/conversations/.gitignore @@ -0,0 +1,37 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo + +service-worker.js diff --git a/src/frontend/apps/conversations/.prettierignore b/src/frontend/apps/conversations/.prettierignore new file mode 100644 index 0000000..15b2044 --- /dev/null +++ b/src/frontend/apps/conversations/.prettierignore @@ -0,0 +1,2 @@ +next-env.d.ts +service-worker.js diff --git a/src/frontend/apps/conversations/README.md b/src/frontend/apps/conversations/README.md new file mode 100644 index 0000000..c403366 --- /dev/null +++ b/src/frontend/apps/conversations/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/src/frontend/apps/conversations/conf/default.conf b/src/frontend/apps/conversations/conf/default.conf new file mode 100644 index 0000000..138ed7d --- /dev/null +++ b/src/frontend/apps/conversations/conf/default.conf @@ -0,0 +1,20 @@ +server { + listen 8080; + listen 3000; + server_name localhost; + + root /usr/share/nginx/html; + + location / { + try_files $uri index.html $uri/ =404; + } + + location ~ "^/chat/[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/?$" { + try_files $uri /chat/[id]/index.html; + } + + error_page 404 /404.html; + location = /404.html { + internal; + } +} diff --git a/src/frontend/apps/conversations/cunningham.ts b/src/frontend/apps/conversations/cunningham.ts new file mode 100644 index 0000000..8122d62 --- /dev/null +++ b/src/frontend/apps/conversations/cunningham.ts @@ -0,0 +1,258 @@ +import { cunninghamConfig as tokens } from '@gouvfr-lasuite/ui-kit'; + +const customColors = { + 'primary-action': '#1212FF', + 'primary-bg': '#FAFAFA', + 'primary-focus': '#0A76F6', + 'secondary-icon': 'var(--c--theme--colors--primary-text)', + 'blue-400': '#7AB1E8', + 'blue-500': '#417DC4', + 'blue-600': '#3558A2', + 'brown-400': '#E6BE92', + 'brown-500': '#BD987A', + 'brown-600': '#745B47', + 'cyan-400': '#34BAB5', + 'cyan-500': '#009099', + 'cyan-600': '#006A6F', + 'gold-400': '#FFCA00', + 'gold-500': '#C3992A', + 'gold-600': '#695240', + 'green-400': '#34CB6A', + 'green-500': '#00A95F', + 'green-600': '#297254', + 'olive-400': '#99C221', + 'olive-500': '#68A532', + 'olive-600': '#447049', + 'orange-400': '#FF732C', + 'orange-500': '#E4794A', + 'orange-600': '#755348', + 'pink-400': '#FFB7AE', + 'pink-500': '#E18B76', + 'pink-600': '#8D533E', + 'purple-400': '#CE70CC', + 'purple-500': '#A558A0', + 'purple-600': '#6E445A', + 'yellow-400': '#D8C634', + 'yellow-500': '#B7A73F', + 'yellow-600': '#66673D', +}; + +tokens.themes.default.theme = { + ...tokens.themes.default.theme, + ...{ + logo: { + src: '', + alt: '', + widthHeader: '', + widthFooter: '', + }, + colors: { + ...tokens.themes.default.theme.colors, + ...customColors, + }, + }, +}; + +tokens.themes.default.components = { + ...tokens.themes.default.components, + ...{ + 'la-gaufre': false, + 'home-proconnect': false, + alpha: false, + beta: false, + 'image-system-filter': '', + favicon: { + ico: '/assets/favicon-light.ico', + 'png-light': '/assets/favicon-light.png', + 'png-dark': '/assets/favicon-dark.png', + }, + }, +}; + +const dsfrTheme = { + dsfr: { + theme: { + colors: { + 'secondary-icon': '#C9191E', + }, + logo: { + src: '/assets/logo-gouv.svg', + widthHeader: '110px', + widthFooter: '220px', + alt: 'Gouvernement Logo', + }, + }, + components: { + 'la-gaufre': true, + 'home-proconnect': true, + alpha: true, + beta: true, + favicon: { + ico: '/assets/favicon-dsfr.ico', + 'png-light': '/assets/favicon-dsfr.png', + 'png-dark': '/assets/favicon-dark-dsfr.png', + }, + }, + }, +}; + +const genericTheme = { + generic: { + theme: { + colors: { + 'primary-action': '#206EBD', + 'primary-focus': '#1E64BF', + 'primary-text': '#2E2C28', + 'primary-050': '#F8F8F7', + 'primary-100': '#F0EFEC', + 'primary-150': '#F4F4FD', + 'primary-200': '#E8E7E4', + 'primary-300': '#CFCDC9', + 'primary-400': '#979592', + 'primary-500': '#82807D', + 'primary-600': '#3F3D39', + 'primary-700': '#2E2C28', + 'primary-800': '#302E29', + 'primary-900': '#282622', + 'primary-950': '#201F1C', + 'secondary-text': '#fff', + 'secondary-50': '#F4F7FA', + 'secondary-100': '#D7E3EE', + 'secondary-200': '#B8CCE1', + 'secondary-300': '#99B4D3', + 'secondary-400': '#7595BE', + 'secondary-500': '#5874A0', + 'secondary-600': '#3A5383', + 'secondary-700': '#1E3462', + 'secondary-800': '#091B41', + 'secondary-900': '#08183B', + 'secondary-950': '#071636', + 'greyscale-text': '#3C3B38', + 'greyscale-000': '#fff', + 'greyscale-050': '#F8F7F7', + 'greyscale-100': '#F3F3F2', + 'greyscale-200': '#ECEBEA', + 'greyscale-250': '#E4E3E2', + 'greyscale-300': '#D3D2CF', + 'greyscale-350': '#eee', + 'greyscale-400': '#96948E', + 'greyscale-500': '#817E77', + 'greyscale-600': '#6A6862', + 'greyscale-700': '#3C3B38', + 'greyscale-750': '#383632', + 'greyscale-800': '#2D2B27', + 'greyscale-900': '#262522', + 'greyscale-950': '#201F1C', + 'greyscale-1000': '#181714', + 'success-text': '#234935', + 'success-50': '#F3FBF5', + 'success-100': '#E4F7EA', + 'success-200': '#CAEED4', + 'success-300': '#A0E0B5', + 'success-400': '#6CC88C', + 'success-500': '#6CC88C', + 'success-600': '#358D5C', + 'success-700': '#2D704B', + 'success-800': '#28583F', + 'success-900': '#234935', + 'success-950': '#0F281B', + 'info-text': '#212445', + 'info-50': '#F2F6FB', + 'info-100': '#E2E9F5', + 'info-200': '#CCD8EE', + 'info-300': '#A9C0E3', + 'info-400': '#809DD4', + 'info-500': '#617BC7', + 'info-600': '#4A5CBF', + 'info-700': '#3E49B2', + 'info-800': '#353C8F', + 'info-900': '#303771', + 'info-950': '#212445', + 'warning-text': '#D97C3A', + 'warning-50': '#FDF7F1', + 'warning-100': '#FBEDDC', + 'warning-200': '#F5D9B9', + 'warning-300': '#EDBE8C', + 'warning-400': '#E2985C', + 'warning-500': '#D97C3A', + 'warning-600': '#C96330', + 'warning-700': '#A34B32', + 'warning-800': '#813B2C', + 'warning-900': '#693327', + 'warning-950': '#381713', + 'danger-action': '#C0182A', + 'danger-text': '#FFF', + 'danger-050': '#FDF5F4', + 'danger-100': '#FBEBE8', + 'danger-200': '#F9E0DC', + 'danger-300': '#F3C3BD', + 'danger-400': '#E26552', + 'danger-500': '#C91F00', + 'danger-600': '#A71901', + 'danger-700': '#562C2B', + 'danger-800': '#392425', + 'danger-900': '#311F20', + 'danger-950': '#2A191A', + 'blue-400': '#8BAECC', + 'blue-500': '#567AA2', + 'blue-600': '#455784', + 'brown-400': '#E4C090', + 'brown-500': '#BA9977', + 'brown-600': '#735C45', + 'cyan-400': '#5CBEC9', + 'cyan-500': '#43A1B3', + 'cyan-600': '#39809B', + 'gold-400': '#ECBF50', + 'gold-500': '#DFA038', + 'gold-600': '#C17B31', + 'green-400': '#5DBD9A', + 'green-500': '#3AA183', + 'green-600': '#2A816D', + 'olive-400': '#AFD662', + 'olive-500': '#90BB4B', + 'olive-600': '#6E9441', + 'orange-400': '#E2985C', + 'orange-500': '#D97C3A', + 'orange-600': '#C96330', + 'pink-400': '#BE8FC8', + 'pink-500': '#A563B1', + 'pink-600': '#8B44A5', + 'purple-400': '#BE8FC8', + 'purple-500': '#A563B1', + 'purple-600': '#8B44A5', + 'yellow-400': '#EDC947', + 'yellow-500': '#DBB13A', + 'yellow-600': '#B88A34', + }, + font: { + families: { + base: 'Inter, Roboto Flex Variable, sans-serif', + accent: 'Inter, Roboto Flex Variable, sans-serif', + }, + }, + }, + components: { + button: { + primary: { + background: { + 'color-hover': 'var(--c--theme--colors--primary-focus)', + 'color-active': 'var(--c--theme--colors--primary-focus)', + 'color-focus': 'var(--c--theme--colors--primary-focus)', + }, + }, + }, + 'image-system-filter': 'saturate(0.2)', + }, + }, +}; + +const docsTokens = { + ...tokens, + themes: { + ...tokens.themes, + ...dsfrTheme, + ...genericTheme, + }, +}; + +export default docsTokens; diff --git a/src/frontend/apps/conversations/jest.config.ts b/src/frontend/apps/conversations/jest.config.ts new file mode 100644 index 0000000..05bfb0f --- /dev/null +++ b/src/frontend/apps/conversations/jest.config.ts @@ -0,0 +1,33 @@ +import type { Config } from 'jest'; +import nextJest from 'next/jest.js'; + +const createJestConfig = nextJest({ + dir: './', +}); + +// Add any custom config to be passed to Jest +const config: Config = { + coverageProvider: 'v8', + moduleNameMapper: { + '^@/docs/(.*)$': '/src/features/docs/$1', + '^@/(.*)$': '/src/$1', + }, + setupFilesAfterEnv: ['/jest.setup.ts'], + testEnvironment: 'jsdom', +}; + +const jestConfig = async () => { + const nextJestConfig = await createJestConfig(config)(); + return { + ...nextJestConfig, + moduleNameMapper: { + '\\.svg$': '/jest/mocks/svg.js', + '^.+\\.svg\\?url$': `/jest/mocks/fileMock.js`, + BlockNoteEditor: `/jest/mocks/ComponentMock.js`, + 'custom-blocks': `/jest/mocks/ComponentMock.js`, + ...nextJestConfig.moduleNameMapper, + }, + }; +}; + +export default jestConfig; diff --git a/src/frontend/apps/conversations/jest.setup.ts b/src/frontend/apps/conversations/jest.setup.ts new file mode 100644 index 0000000..564d8a6 --- /dev/null +++ b/src/frontend/apps/conversations/jest.setup.ts @@ -0,0 +1,4 @@ +import '@testing-library/jest-dom'; +import * as dotenv from 'dotenv'; + +dotenv.config({ path: './.env.test' }); diff --git a/src/frontend/apps/conversations/jest/mocks/ComponentMock.js b/src/frontend/apps/conversations/jest/mocks/ComponentMock.js new file mode 100644 index 0000000..812a08b --- /dev/null +++ b/src/frontend/apps/conversations/jest/mocks/ComponentMock.js @@ -0,0 +1,5 @@ +import React from 'react'; + +export const ComponentMock = () => { + return
My component mocked
; +}; diff --git a/src/frontend/apps/conversations/jest/mocks/fileMock.js b/src/frontend/apps/conversations/jest/mocks/fileMock.js new file mode 100644 index 0000000..28a2498 --- /dev/null +++ b/src/frontend/apps/conversations/jest/mocks/fileMock.js @@ -0,0 +1,16 @@ +module.exports = { + src: '/img.jpg', + height: 40, + width: 40, + blurDataURL: 'data:image/png;base64,imagedata', +}; + +if ( + (typeof exports.default === 'function' || + (typeof exports.default === 'object' && exports.default !== null)) && + typeof exports.default.__esModule === 'undefined' +) { + Object.defineProperty(exports.default, '__esModule', { value: true }); + Object.assign(exports.default, exports); + module.exports = exports.default; +} diff --git a/src/frontend/apps/conversations/jest/mocks/svg.js b/src/frontend/apps/conversations/jest/mocks/svg.js new file mode 100644 index 0000000..0b7fc5b --- /dev/null +++ b/src/frontend/apps/conversations/jest/mocks/svg.js @@ -0,0 +1,3 @@ +const nameMock = 'svg'; +export default nameMock; +export const ReactComponent = 'svg'; diff --git a/src/frontend/apps/conversations/next-env.d.ts b/src/frontend/apps/conversations/next-env.d.ts new file mode 100644 index 0000000..52e831b --- /dev/null +++ b/src/frontend/apps/conversations/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information. diff --git a/src/frontend/apps/conversations/next.config.js b/src/frontend/apps/conversations/next.config.js new file mode 100644 index 0000000..51cb587 --- /dev/null +++ b/src/frontend/apps/conversations/next.config.js @@ -0,0 +1,51 @@ +const crypto = require('crypto'); + +const { InjectManifest } = require('workbox-webpack-plugin'); + +const buildId = crypto.randomBytes(256).toString('hex').slice(0, 8); + +/** @type {import('next').NextConfig} */ +const nextConfig = { + output: 'export', + trailingSlash: true, + images: { + unoptimized: true, + }, + compiler: { + // Enables the styled-components SWC transform + styledComponents: true, + }, + generateBuildId: () => buildId, + env: { + NEXT_PUBLIC_BUILD_ID: buildId, + }, + webpack(config, { isServer }) { + // Grab the existing rule that handles SVG imports + const fileLoaderRule = config.module.rules.find((rule) => + rule.test?.test?.('.svg'), + ); + + config.module.rules.push( + // Reapply the existing rule, but only for svg imports ending in ?url + { + ...fileLoaderRule, + test: /\.svg$/i, + resourceQuery: /url/, // *.svg?url + }, + // Convert all other *.svg imports to React components + { + test: /\.svg$/i, + issuer: fileLoaderRule.issuer, + resourceQuery: { not: [...fileLoaderRule.resourceQuery.not, /url/] }, // exclude if *.svg?url + use: ['@svgr/webpack'], + }, + ); + + // Modify the file loader rule to ignore *.svg, since we have it handled now. + fileLoaderRule.exclude = /\.svg$/i; + + return config; + }, +}; + +module.exports = nextConfig; diff --git a/src/frontend/apps/conversations/package.json b/src/frontend/apps/conversations/package.json new file mode 100644 index 0000000..24eb650 --- /dev/null +++ b/src/frontend/apps/conversations/package.json @@ -0,0 +1,83 @@ +{ + "name": "app-conversations", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "next dev", + "build": "prettier --check . && yarn stylelint && next build", + "build:ci": "cp .env.development .env.local && yarn build", + "build-theme": "cunningham -g css,ts -o src/cunningham --utility-classes && yarn prettier && yarn stylelint --fix", + "start": "npx -y serve@latest out", + "lint": "tsc --noEmit && next lint", + "prettier": "prettier --write .", + "stylelint": "stylelint \"**/*.css\"", + "test": "jest", + "test:watch": "jest --watch" + }, + "dependencies": { + "@ag-media/react-pdf-table": "2.0.3", + "@ai-sdk/react": "1.2.12", + "@ai-sdk/ui-utils": "1.2.11", + "@emoji-mart/data": "1.2.1", + "@emoji-mart/react": "1.1.1", + "@fontsource/material-icons": "5.2.5", + "@gouvfr-lasuite/integration": "1.0.3", + "@gouvfr-lasuite/ui-kit": "0.7.0", + "@openfun/cunningham-react": "3.1.0", + "@react-pdf/renderer": "4.3.0", + "@sentry/nextjs": "9.26.0", + "@tanstack/react-query": "5.80.5", + "canvg": "4.0.3", + "clsx": "2.1.1", + "cmdk": "1.1.1", + "crisp-sdk-web": "1.0.25", + "emoji-mart": "5.6.0", + "i18next": "25.2.1", + "i18next-browser-languagedetector": "8.1.0", + "idb": "8.0.3", + "lodash": "4.17.21", + "luxon": "3.6.1", + "next": "15.3.3", + "posthog-js": "1.249.3", + "react": "*", + "react-aria-components": "1.9.0", + "react-dom": "*", + "react-i18next": "15.5.2", + "react-intersection-observer": "9.16.0", + "react-markdown": "10.1.0", + "react-select": "5.10.1", + "remark-gfm": "4.0.1", + "styled-components": "6.1.18", + "use-debounce": "10.0.4", + "zod": "^3.25.67", + "zustand": "5.0.5" + }, + "devDependencies": { + "@svgr/webpack": "8.1.0", + "@tanstack/react-query-devtools": "5.80.5", + "@testing-library/dom": "10.4.0", + "@testing-library/jest-dom": "6.6.3", + "@testing-library/react": "16.3.0", + "@testing-library/user-event": "14.6.1", + "@types/jest": "29.5.14", + "@types/lodash": "4.17.17", + "@types/luxon": "3.6.2", + "@types/node": "*", + "@types/react": "*", + "@types/react-dom": "*", + "cross-env": "7.0.3", + "dotenv": "16.5.0", + "eslint-config-conversations": "*", + "fetch-mock": "9.11.0", + "jest": "29.7.0", + "jest-environment-jsdom": "29.7.0", + "node-fetch": "2.7.0", + "prettier": "3.5.3", + "stylelint": "16.20.0", + "stylelint-config-standard": "38.0.0", + "stylelint-prettier": "5.0.3", + "typescript": "*", + "webpack": "5.99.9", + "workbox-webpack-plugin": "7.1.0" + } +} diff --git a/src/frontend/apps/conversations/public/assets/SC1-en.webm b/src/frontend/apps/conversations/public/assets/SC1-en.webm new file mode 100644 index 0000000..ff1dc28 Binary files /dev/null and b/src/frontend/apps/conversations/public/assets/SC1-en.webm differ diff --git a/src/frontend/apps/conversations/public/assets/SC1-fr.webm b/src/frontend/apps/conversations/public/assets/SC1-fr.webm new file mode 100644 index 0000000..1ebc7be Binary files /dev/null and b/src/frontend/apps/conversations/public/assets/SC1-fr.webm differ diff --git a/src/frontend/apps/conversations/public/assets/favicon-dark-dsfr.png b/src/frontend/apps/conversations/public/assets/favicon-dark-dsfr.png new file mode 100644 index 0000000..73603e6 Binary files /dev/null and b/src/frontend/apps/conversations/public/assets/favicon-dark-dsfr.png differ diff --git a/src/frontend/apps/conversations/public/assets/favicon-dark.png b/src/frontend/apps/conversations/public/assets/favicon-dark.png new file mode 100644 index 0000000..1d05fec Binary files /dev/null and b/src/frontend/apps/conversations/public/assets/favicon-dark.png differ diff --git a/src/frontend/apps/conversations/public/assets/favicon-dsfr.ico b/src/frontend/apps/conversations/public/assets/favicon-dsfr.ico new file mode 100644 index 0000000..9763027 Binary files /dev/null and b/src/frontend/apps/conversations/public/assets/favicon-dsfr.ico differ diff --git a/src/frontend/apps/conversations/public/assets/favicon-dsfr.png b/src/frontend/apps/conversations/public/assets/favicon-dsfr.png new file mode 100644 index 0000000..4c433bb Binary files /dev/null and b/src/frontend/apps/conversations/public/assets/favicon-dsfr.png differ diff --git a/src/frontend/apps/conversations/public/assets/favicon-light.ico b/src/frontend/apps/conversations/public/assets/favicon-light.ico new file mode 100644 index 0000000..b4255b9 Binary files /dev/null and b/src/frontend/apps/conversations/public/assets/favicon-light.ico differ diff --git a/src/frontend/apps/conversations/public/assets/favicon-light.png b/src/frontend/apps/conversations/public/assets/favicon-light.png new file mode 100644 index 0000000..19cc87d Binary files /dev/null and b/src/frontend/apps/conversations/public/assets/favicon-light.png differ diff --git a/src/frontend/apps/conversations/public/assets/icon-docs.svg b/src/frontend/apps/conversations/public/assets/icon-docs.svg new file mode 100644 index 0000000..05cf043 --- /dev/null +++ b/src/frontend/apps/conversations/public/assets/icon-docs.svg @@ -0,0 +1,12 @@ + + + + diff --git a/src/frontend/apps/conversations/public/assets/img-not-found.svg b/src/frontend/apps/conversations/public/assets/img-not-found.svg new file mode 100644 index 0000000..7039b94 --- /dev/null +++ b/src/frontend/apps/conversations/public/assets/img-not-found.svg @@ -0,0 +1,29 @@ + + + + + + no-image + + + + diff --git a/src/frontend/apps/conversations/public/assets/logo-gouv.png b/src/frontend/apps/conversations/public/assets/logo-gouv.png new file mode 100644 index 0000000..f9a07d8 Binary files /dev/null and b/src/frontend/apps/conversations/public/assets/logo-gouv.png differ diff --git a/src/frontend/apps/conversations/public/assets/logo-gouv.svg b/src/frontend/apps/conversations/public/assets/logo-gouv.svg new file mode 100644 index 0000000..824e1b9 --- /dev/null +++ b/src/frontend/apps/conversations/public/assets/logo-gouv.svg @@ -0,0 +1,454 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/frontend/apps/conversations/public/assets/logo-pdf.svg b/src/frontend/apps/conversations/public/assets/logo-pdf.svg new file mode 100644 index 0000000..19cf515 --- /dev/null +++ b/src/frontend/apps/conversations/public/assets/logo-pdf.svg @@ -0,0 +1,248 @@ + + + + + + + + + + + + + + + + + diff --git a/src/frontend/apps/conversations/public/assets/logo-suite-numerique.png b/src/frontend/apps/conversations/public/assets/logo-suite-numerique.png new file mode 100644 index 0000000..840e972 Binary files /dev/null and b/src/frontend/apps/conversations/public/assets/logo-suite-numerique.png differ diff --git a/src/frontend/apps/conversations/public/assets/mail-header-background.png b/src/frontend/apps/conversations/public/assets/mail-header-background.png new file mode 100644 index 0000000..d922bd3 Binary files /dev/null and b/src/frontend/apps/conversations/public/assets/mail-header-background.png differ diff --git a/src/frontend/apps/conversations/src/api/APIError.ts b/src/frontend/apps/conversations/src/api/APIError.ts new file mode 100644 index 0000000..6d7b92b --- /dev/null +++ b/src/frontend/apps/conversations/src/api/APIError.ts @@ -0,0 +1,52 @@ +/** + * Generic interface for representing an API error structure. + * + * @template T - Optional type of additional data returned with the error. + */ +interface IAPIError { + /** HTTP status code or API-defined error code */ + status: number; + /** Optional list of error causes (e.g., validation issues) */ + cause?: string[]; + /** Optional extra data provided with the error */ + data?: T; +} + +/** + * Custom error class for representing API errors. + * Extends the native Error object with additional context such as HTTP status, + * causes, and extra data returned by the API. + * + * @template T - Optional type of the `data` field + */ +export class APIError extends Error implements IAPIError { + public status: IAPIError['status']; + public cause?: IAPIError['cause']; + public data?: IAPIError['data']; + + /** + * Constructs a new APIError instance. + * + * @param message - The human-readable error message. + * @param status - The HTTP status code or equivalent. + * @param cause - (Optional) List of strings describing error causes. + * @param data - (Optional) Any additional data returned by the API. + */ + constructor(message: string, { status, cause, data }: IAPIError) { + super(message); + this.name = 'APIError'; + this.status = status; + this.cause = cause; + this.data = data; + } +} + +/** + * Type guard for checking if a value is an instance of APIError. + * + * @param error - The value to check. + * @returns True if the value is an instance of APIError. + */ +export const isAPIError = (error: unknown): error is APIError => { + return error instanceof APIError; +}; diff --git a/src/frontend/apps/conversations/src/api/__tests__/APIError.test.ts b/src/frontend/apps/conversations/src/api/__tests__/APIError.test.ts new file mode 100644 index 0000000..395a546 --- /dev/null +++ b/src/frontend/apps/conversations/src/api/__tests__/APIError.test.ts @@ -0,0 +1,36 @@ +import { APIError, isAPIError } from '@/api'; + +describe('APIError', () => { + it('should correctly instantiate with required fields', () => { + const error = new APIError('Something went wrong', { status: 500 }); + + expect(error).toBeInstanceOf(Error); + expect(error).toBeInstanceOf(APIError); + expect(error.message).toBe('Something went wrong'); + expect(error.status).toBe(500); + expect(error.cause).toBeUndefined(); + expect(error.data).toBeUndefined(); + }); + + it('should correctly instantiate with all fields', () => { + const details = { field: 'email' }; + const error = new APIError('Validation failed', { + status: 400, + cause: ['Invalid email format'], + data: details, + }); + + expect(error.name).toBe('APIError'); + expect(error.status).toBe(400); + expect(error.cause).toEqual(['Invalid email format']); + expect(error.data).toEqual(details); + }); + + it('should be detected by isAPIError type guard', () => { + const error = new APIError('Unauthorized', { status: 401 }); + const notAnError = { message: 'Fake error' }; + + expect(isAPIError(error)).toBe(true); + expect(isAPIError(notAnError)).toBe(false); + }); +}); diff --git a/src/frontend/apps/conversations/src/api/__tests__/config.test.ts b/src/frontend/apps/conversations/src/api/__tests__/config.test.ts new file mode 100644 index 0000000..cb9bf26 --- /dev/null +++ b/src/frontend/apps/conversations/src/api/__tests__/config.test.ts @@ -0,0 +1,16 @@ +import { baseApiUrl } from '@/api'; + +describe('config', () => { + it('constructs URL with default version', () => { + expect(baseApiUrl()).toBe('http://test.jest/api/v1.0/'); + }); + + it('constructs URL with custom version', () => { + expect(baseApiUrl('2.0')).toBe('http://test.jest/api/v2.0/'); + }); + + it('uses env origin if available', () => { + process.env.NEXT_PUBLIC_API_ORIGIN = 'https://env.example.com'; + expect(baseApiUrl('3.0')).toBe('https://env.example.com/api/v3.0/'); + }); +}); diff --git a/src/frontend/apps/conversations/src/api/__tests__/fetchApi.test.tsx b/src/frontend/apps/conversations/src/api/__tests__/fetchApi.test.tsx new file mode 100644 index 0000000..f65b450 --- /dev/null +++ b/src/frontend/apps/conversations/src/api/__tests__/fetchApi.test.tsx @@ -0,0 +1,48 @@ +import fetchMock from 'fetch-mock'; + +import { fetchAPI } from '@/api'; + +describe('fetchAPI', () => { + beforeEach(() => { + fetchMock.restore(); + }); + + it('adds correctly the basename', () => { + fetchMock.mock('http://test.jest/api/v1.0/some/url', 200); + + void fetchAPI('some/url'); + + expect(fetchMock.lastUrl()).toEqual('http://test.jest/api/v1.0/some/url'); + }); + + it('adds the credentials automatically', () => { + fetchMock.mock('http://test.jest/api/v1.0/some/url', 200); + + void fetchAPI('some/url', { body: 'some body' }); + + expect(fetchMock.lastOptions()).toEqual({ + body: 'some body', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + }, + }); + }); + + it('check the versioning', () => { + fetchMock.mock('http://test.jest/api/v2.0/some/url', 200); + + void fetchAPI('some/url', {}, '2.0'); + + expect(fetchMock.lastUrl()).toEqual('http://test.jest/api/v2.0/some/url'); + }); + + it('removes Content-Type header when withoutContentType is true', async () => { + fetchMock.mock('http://test.jest/api/v1.0/some/url', 200); + + await fetchAPI('some/url', { withoutContentType: true }); + + const options = fetchMock.lastOptions(); + expect(options?.headers).not.toHaveProperty('Content-Type'); + }); +}); diff --git a/src/frontend/apps/conversations/src/api/__tests__/helpers.test.tsx b/src/frontend/apps/conversations/src/api/__tests__/helpers.test.tsx new file mode 100644 index 0000000..e470636 --- /dev/null +++ b/src/frontend/apps/conversations/src/api/__tests__/helpers.test.tsx @@ -0,0 +1,59 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { renderHook, waitFor } from '@testing-library/react'; + +import { useAPIInfiniteQuery } from '@/api'; + +interface DummyItem { + id: number; +} + +interface DummyResponse { + results: DummyItem[]; + next?: string; +} + +const createWrapper = () => { + const queryClient = new QueryClient(); + return ({ children }: { children: React.ReactNode }) => ( + {children} + ); +}; + +describe('helpers', () => { + it('fetches and paginates correctly', async () => { + const mockAPI = jest + .fn, [{ page: number; query: string }]>() + .mockResolvedValueOnce({ + results: [{ id: 1 }], + next: 'url?page=2', + }) + .mockResolvedValueOnce({ + results: [{ id: 2 }], + next: undefined, + }); + + const { result } = renderHook( + () => useAPIInfiniteQuery('test-key', mockAPI, { query: 'test' }), + { wrapper: createWrapper() }, + ); + + // Wait for first page + await waitFor(() => { + expect(result.current.data?.pages[0].results[0].id).toBe(1); + }); + + // Fetch next page + await result.current.fetchNextPage(); + + await waitFor(() => { + expect(result.current.data?.pages.length).toBe(2); + }); + + await waitFor(() => { + expect(result.current.data?.pages[1].results[0].id).toBe(2); + }); + + expect(mockAPI).toHaveBeenCalledWith({ query: 'test', page: 1 }); + expect(mockAPI).toHaveBeenCalledWith({ query: 'test', page: 2 }); + }); +}); diff --git a/src/frontend/apps/conversations/src/api/__tests__/utils.test.ts b/src/frontend/apps/conversations/src/api/__tests__/utils.test.ts new file mode 100644 index 0000000..8643318 --- /dev/null +++ b/src/frontend/apps/conversations/src/api/__tests__/utils.test.ts @@ -0,0 +1,57 @@ +import { errorCauses, getCSRFToken } from '@/api'; + +describe('utils', () => { + describe('errorCauses', () => { + const createMockResponse = (jsonData: any, status = 400): Response => { + return { + status, + json: () => jsonData, + } as unknown as Response; + }; + + it('parses multiple string causes from error body', async () => { + const mockResponse = createMockResponse( + { + field: ['error message 1', 'error message 2'], + }, + 400, + ); + + const result = await errorCauses(mockResponse, { context: 'login' }); + + expect(result.status).toBe(400); + expect(result.cause).toEqual(['error message 1', 'error message 2']); + expect(result.data).toEqual({ context: 'login' }); + }); + + it('returns undefined causes if no JSON body', async () => { + const mockResponse = createMockResponse(null, 500); + + const result = await errorCauses(mockResponse); + + expect(result.status).toBe(500); + expect(result.cause).toBeUndefined(); + expect(result.data).toBeUndefined(); + }); + }); + + describe('getCSRFToken', () => { + it('extracts csrftoken from document.cookie', () => { + Object.defineProperty(document, 'cookie', { + writable: true, + value: 'sessionid=xyz; csrftoken=abc123; theme=dark', + }); + + expect(getCSRFToken()).toBe('abc123'); + }); + + it('returns undefined if csrftoken is not present', () => { + Object.defineProperty(document, 'cookie', { + writable: true, + value: 'sessionid=xyz; theme=dark', + }); + + expect(getCSRFToken()).toBeUndefined(); + }); + }); +}); diff --git a/src/frontend/apps/conversations/src/api/config.ts b/src/frontend/apps/conversations/src/api/config.ts new file mode 100644 index 0000000..916585e --- /dev/null +++ b/src/frontend/apps/conversations/src/api/config.ts @@ -0,0 +1,22 @@ +/** + * Returns the base URL for the backend API. + * + * Priority: + * 1. Uses NEXT_PUBLIC_API_ORIGIN from environment variables if defined. + * 2. Falls back to the browser's window.location.origin if in a browser environment. + * 3. Defaults to an empty string if executed in a non-browser environment without the env variable. + * + * @returns The backend base URL as a string. + */ +export const backendUrl = () => + process.env.NEXT_PUBLIC_API_ORIGIN || + (typeof window !== 'undefined' ? window.location.origin : ''); + +/** + * Constructs the full base API URL, including the versioned path (e.g., `/api/v1.0/`). + * + * @param apiVersion - The version of the API (defaults to '1.0'). + * @returns The full versioned API base URL as a string. + */ +export const baseApiUrl = (apiVersion: string = '1.0') => + `${backendUrl()}/api/v${apiVersion}/`; diff --git a/src/frontend/apps/conversations/src/api/fetchApi.ts b/src/frontend/apps/conversations/src/api/fetchApi.ts new file mode 100644 index 0000000..10d5f1d --- /dev/null +++ b/src/frontend/apps/conversations/src/api/fetchApi.ts @@ -0,0 +1,31 @@ +import { baseApiUrl } from './config'; +import { getCSRFToken } from './utils'; + +interface FetchAPIInit extends RequestInit { + withoutContentType?: boolean; +} + +export const fetchAPI = async ( + input: string, + init?: FetchAPIInit, + apiVersion = '1.0', +) => { + const apiUrl = `${baseApiUrl(apiVersion)}${input}`; + const csrfToken = getCSRFToken(); + + const headers = { + 'Content-Type': 'application/json', + ...init?.headers, + ...(csrfToken && { 'X-CSRFToken': csrfToken }), + }; + + if (init?.withoutContentType) { + delete headers?.['Content-Type' as keyof typeof headers]; + } + + return await fetch(apiUrl, { + ...init, + credentials: 'include', + headers, + }); +}; diff --git a/src/frontend/apps/conversations/src/api/helpers.tsx b/src/frontend/apps/conversations/src/api/helpers.tsx new file mode 100644 index 0000000..e36b9d4 --- /dev/null +++ b/src/frontend/apps/conversations/src/api/helpers.tsx @@ -0,0 +1,56 @@ +import { + DefinedInitialDataInfiniteOptions, + InfiniteData, + QueryKey, + UseQueryOptions, + useInfiniteQuery, +} from '@tanstack/react-query'; + +import { APIError } from './APIError'; +import { APIList } from './types'; + +export type UseQueryOptionsAPI = UseQueryOptions; +export type DefinedInitialDataInfiniteOptionsAPI< + Q, + TPageParam = number, +> = DefinedInitialDataInfiniteOptions< + Q, + APIError, + InfiniteData, + QueryKey, + TPageParam +>; + +/** + * Custom React hook that wraps React Query's `useInfiniteQuery` for paginated API requests. + * + * @template T - Type of the request parameters. + * @template Q - Type of the API response, which must include an optional `next` field for pagination. + * + * @param {string} key - Unique key to identify the query in the cache. + * @param {(props: T & { page: number }) => Promise} api - Function that fetches paginated data from the API. It receives the params merged with a page number. + * @param {T} param - Static parameters to send with every API request (excluding the page number). + * @param {DefinedInitialDataInfiniteOptionsAPI} [queryConfig] - Optional configuration passed to `useInfiniteQuery` (e.g., stale time, cache time). + * + * @returns Return value of `useInfiniteQuery`, including data, loading state, fetchNextPage, etc. + */ +export const useAPIInfiniteQuery = ['next'] }>( + key: string, + api: (props: T & { page: number }) => Promise, + param: T, + queryConfig?: DefinedInitialDataInfiniteOptionsAPI, +) => { + return useInfiniteQuery, QueryKey, number>({ + initialPageParam: 1, + queryKey: [key, param], + queryFn: ({ pageParam }) => + api({ + ...param, + page: pageParam, + }), + getNextPageParam(lastPage, allPages) { + return lastPage.next ? allPages.length + 1 : undefined; + }, + ...queryConfig, + }); +}; diff --git a/src/frontend/apps/conversations/src/api/index.ts b/src/frontend/apps/conversations/src/api/index.ts new file mode 100644 index 0000000..1d742ad --- /dev/null +++ b/src/frontend/apps/conversations/src/api/index.ts @@ -0,0 +1,6 @@ +export * from './APIError'; +export * from './config'; +export * from './fetchApi'; +export * from './helpers'; +export * from './types'; +export * from './utils'; diff --git a/src/frontend/apps/conversations/src/api/types.ts b/src/frontend/apps/conversations/src/api/types.ts new file mode 100644 index 0000000..e69a61a --- /dev/null +++ b/src/frontend/apps/conversations/src/api/types.ts @@ -0,0 +1,20 @@ +/** + * Generic interface representing a paginated API response. + * + * Commonly used for endpoints that return list results with pagination metadata. + * + * @template T - The type of items in the `results` array. + */ +export interface APIList { + /** Total number of items across all pages */ + count: number; + + /** URL to the next page of results, if available (can be null or undefined) */ + next?: string | null; + + /** URL to the previous page of results, if available (can be null or undefined) */ + previous?: string | null; + + /** The list of items for the current page */ + results: T[]; +} diff --git a/src/frontend/apps/conversations/src/api/utils.ts b/src/frontend/apps/conversations/src/api/utils.ts new file mode 100644 index 0000000..82bbe50 --- /dev/null +++ b/src/frontend/apps/conversations/src/api/utils.ts @@ -0,0 +1,46 @@ +/** + * Extracts error information from an HTTP `Response` object. + * + * This is typically used to parse structured error responses from an API + * and normalize them into a consistent format with `status`, `cause`, and optional `data`. + * + * @param response - The HTTP response object from `fetch()`. + * @param data - Optional custom data to include with the error output. + * @returns An object containing: + * - `status`: HTTP status code from the response + * - `cause`: A flattened list of error messages, or undefined if no body + * - `data`: The optional data passed in + */ +export const errorCauses = async (response: Response, data?: unknown) => { + const errorsBody = (await response.json()) as Record< + string, + string | string[] + > | null; + + const causes = errorsBody + ? Object.entries(errorsBody) + .map(([, value]) => value) + .flat() + : undefined; + + return { + status: response.status, + cause: causes, + data, + }; +}; + +/** + * Retrieves the CSRF token from the browser's cookies. + * + * Assumes the CSRF token is stored as a cookie named "csrftoken". + * + * @returns The CSRF token string if found, otherwise `undefined`. + */ +export function getCSRFToken() { + return document.cookie + .split(';') + .filter((cookie) => cookie.trim().startsWith('csrftoken=')) + .map((cookie) => cookie.split('=')[1]) + .pop(); +} diff --git a/src/frontend/apps/conversations/src/assets/icons/icon-401.png b/src/frontend/apps/conversations/src/assets/icons/icon-401.png new file mode 100644 index 0000000..02c7c84 Binary files /dev/null and b/src/frontend/apps/conversations/src/assets/icons/icon-401.png differ diff --git a/src/frontend/apps/conversations/src/assets/icons/icon-403.png b/src/frontend/apps/conversations/src/assets/icons/icon-403.png new file mode 100644 index 0000000..480944f Binary files /dev/null and b/src/frontend/apps/conversations/src/assets/icons/icon-403.png differ diff --git a/src/frontend/apps/conversations/src/assets/icons/icon-404.svg b/src/frontend/apps/conversations/src/assets/icons/icon-404.svg new file mode 100644 index 0000000..fe70571 --- /dev/null +++ b/src/frontend/apps/conversations/src/assets/icons/icon-404.svg @@ -0,0 +1,20 @@ + + + + + + + + \ No newline at end of file diff --git a/src/frontend/apps/conversations/src/assets/icons/icon-docs.svg b/src/frontend/apps/conversations/src/assets/icons/icon-docs.svg new file mode 100644 index 0000000..882e92e --- /dev/null +++ b/src/frontend/apps/conversations/src/assets/icons/icon-docs.svg @@ -0,0 +1,12 @@ + + + + diff --git a/src/frontend/apps/conversations/src/assets/icons/icon-group.svg b/src/frontend/apps/conversations/src/assets/icons/icon-group.svg new file mode 100644 index 0000000..c2a7d3c --- /dev/null +++ b/src/frontend/apps/conversations/src/assets/icons/icon-group.svg @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/src/frontend/apps/conversations/src/assets/icons/icon-group2.svg b/src/frontend/apps/conversations/src/assets/icons/icon-group2.svg new file mode 100644 index 0000000..845da48 --- /dev/null +++ b/src/frontend/apps/conversations/src/assets/icons/icon-group2.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + diff --git a/src/frontend/apps/conversations/src/assets/icons/icon-user.svg b/src/frontend/apps/conversations/src/assets/icons/icon-user.svg new file mode 100644 index 0000000..ff518b5 --- /dev/null +++ b/src/frontend/apps/conversations/src/assets/icons/icon-user.svg @@ -0,0 +1,6 @@ + + + diff --git a/src/frontend/apps/conversations/src/components/Box.tsx b/src/frontend/apps/conversations/src/components/Box.tsx new file mode 100644 index 0000000..84e3205 --- /dev/null +++ b/src/frontend/apps/conversations/src/components/Box.tsx @@ -0,0 +1,99 @@ +import { ComponentPropsWithRef, HTMLElementType } from 'react'; +import styled from 'styled-components'; +import { CSSProperties, RuleSet } from 'styled-components/dist/types'; + +import { + MarginPadding, + stylesMargin, + stylesPadding, +} from '@/utils/styleBuilder'; + +import { hideEffect, showEffect } from './Effect'; + +export interface BoxProps { + as?: HTMLElementType; + $align?: CSSProperties['alignItems']; + $background?: CSSProperties['background']; + $color?: CSSProperties['color']; + $css?: string | RuleSet; + $direction?: CSSProperties['flexDirection']; + $display?: CSSProperties['display']; + $effect?: 'show' | 'hide'; + $flex?: CSSProperties['flex']; + $gap?: CSSProperties['gap']; + $hasTransition?: boolean | 'slow'; + $height?: CSSProperties['height']; + $justify?: CSSProperties['justifyContent']; + $opacity?: CSSProperties['opacity']; + $overflow?: CSSProperties['overflow']; + $margin?: MarginPadding; + $maxHeight?: CSSProperties['maxHeight']; + $minHeight?: CSSProperties['minHeight']; + $maxWidth?: CSSProperties['maxWidth']; + $minWidth?: CSSProperties['minWidth']; + $padding?: MarginPadding; + $position?: CSSProperties['position']; + $radius?: CSSProperties['borderRadius']; + $shrink?: CSSProperties['flexShrink']; + $transition?: CSSProperties['transition']; + $width?: CSSProperties['width']; + $wrap?: CSSProperties['flexWrap']; + $zIndex?: CSSProperties['zIndex']; +} + +export type BoxType = ComponentPropsWithRef; + +export const Box = styled('div')` + display: flex; + flex-direction: column; + ${({ $align }) => $align && `align-items: ${$align};`} + ${({ $background }) => $background && `background: ${$background};`} + ${({ $color }) => $color && `color: ${$color};`} + ${({ $direction }) => $direction && `flex-direction: ${$direction};`} + ${({ $display }) => $display && `display: ${$display};`} + ${({ $flex }) => $flex && `flex: ${$flex};`} + ${({ $gap }) => $gap && `gap: ${$gap};`} + ${({ $height }) => $height && `height: ${$height};`} + ${({ $hasTransition }) => + $hasTransition && $hasTransition === 'slow' + ? `transition: all 0.5s ease-in-out;` + : $hasTransition + ? `transition: all 0.3s ease-in-out;` + : ''} + ${({ $justify }) => $justify && `justify-content: ${$justify};`} + ${({ $margin }) => $margin && stylesMargin($margin)} + ${({ $maxHeight }) => $maxHeight && `max-height: ${$maxHeight};`} + ${({ $minHeight }) => $minHeight && `min-height: ${$minHeight};`} + ${({ $maxWidth }) => $maxWidth && `max-width: ${$maxWidth};`} + ${({ $minWidth }) => $minWidth && `min-width: ${$minWidth};`} + ${({ $opacity }) => $opacity && `opacity: ${$opacity};`} + ${({ $overflow }) => $overflow && `overflow: ${$overflow};`} + ${({ $padding }) => $padding && stylesPadding($padding)} + ${({ $position }) => $position && `position: ${$position};`} + ${({ $radius }) => $radius && `border-radius: ${$radius};`} + ${({ $shrink }) => $shrink && `flex-shrink: ${$shrink};`} + ${({ $transition }) => $transition && `transition: ${$transition};`} + ${({ $width }) => $width && `width: ${$width};`} + ${({ $wrap }) => $wrap && `flex-wrap: ${$wrap};`} + ${({ $css }) => $css && (typeof $css === 'string' ? `${$css};` : $css)} + ${({ $zIndex }) => $zIndex && `z-index: ${$zIndex};`} + ${({ $effect }) => { + let effect; + switch ($effect) { + case 'show': + effect = showEffect; + break; + case 'hide': + effect = hideEffect; + break; + } + + return ( + effect && + ` + transition: all 0.3s ease-in-out; + ${effect} + ` + ); + }} +`; diff --git a/src/frontend/apps/conversations/src/components/BoxButton.tsx b/src/frontend/apps/conversations/src/components/BoxButton.tsx new file mode 100644 index 0000000..3b8d775 --- /dev/null +++ b/src/frontend/apps/conversations/src/components/BoxButton.tsx @@ -0,0 +1,60 @@ +import { forwardRef } from 'react'; +import { css } from 'styled-components'; + +import { Box, BoxType } from './Box'; + +export type BoxButtonType = BoxType & { + disabled?: boolean; +}; + +/** + +/** + * Styleless button that extends the Box component. + * Good to wrap around SVGs or other elements that need to be clickable. + * @param props - @see BoxType props + * @param ref + * @see Box + * @example + * ```tsx + * console.log('clicked')}> + * Click me + * + * ``` + */ +const BoxButton = forwardRef( + ({ $css, ...props }, ref) => { + return ( + ) => { + if (props.disabled) { + return; + } + props.onClick?.(event); + }} + /> + ); + }, +); + +BoxButton.displayName = 'BoxButton'; +export { BoxButton }; diff --git a/src/frontend/apps/conversations/src/components/Card.tsx b/src/frontend/apps/conversations/src/components/Card.tsx new file mode 100644 index 0000000..9e884bf --- /dev/null +++ b/src/frontend/apps/conversations/src/components/Card.tsx @@ -0,0 +1,29 @@ +import { PropsWithChildren } from 'react'; +import { css } from 'styled-components'; + +import { useCunninghamTheme } from '@/cunningham'; + +import { Box, BoxType } from '.'; + +export const Card = ({ + children, + $css, + ...props +}: PropsWithChildren) => { + const { colorsTokens } = useCunninghamTheme(); + + return ( + + {children} + + ); +}; diff --git a/src/frontend/apps/conversations/src/components/DropButton.tsx b/src/frontend/apps/conversations/src/components/DropButton.tsx new file mode 100644 index 0000000..22f18f6 --- /dev/null +++ b/src/frontend/apps/conversations/src/components/DropButton.tsx @@ -0,0 +1,94 @@ +import { + PropsWithChildren, + ReactNode, + useEffect, + useRef, + useState, +} from 'react'; +import { Button, Popover } from 'react-aria-components'; +import styled, { css } from 'styled-components'; + +import { useCunninghamTheme } from '@/cunningham'; + +import { BoxProps } from './Box'; + +const StyledPopover = styled(Popover)` + background-color: white; + border-radius: 4px; + box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1); + border: 1px solid #dddddd; + transition: opacity 0.2s ease-in-out; +`; + +interface StyledButtonProps { + $css?: BoxProps['$css']; +} +const StyledButton = styled(Button)` + cursor: pointer; + border: none; + background: none; + outline: none; + transition: all 0.2s ease-in-out; + font-weight: 500; + font-size: 0.938rem; + padding: 0; + ${({ $css }) => $css}; +`; + +export interface DropButtonProps { + button: ReactNode; + buttonCss?: BoxProps['$css']; + isOpen?: boolean; + onOpenChange?: (isOpen: boolean) => void; + label?: string; +} + +export const DropButton = ({ + button, + buttonCss, + isOpen = false, + onOpenChange, + children, + label, +}: PropsWithChildren) => { + const { themeTokens } = useCunninghamTheme(); + const font = themeTokens['font']?.['families']['base']; + const [isLocalOpen, setIsLocalOpen] = useState(isOpen); + + const triggerRef = useRef(null); + + useEffect(() => { + setIsLocalOpen(isOpen); + }, [isOpen]); + + const onOpenChangeHandler = (isOpen: boolean) => { + setIsLocalOpen(isOpen); + onOpenChange?.(isOpen); + }; + + return ( + <> + onOpenChangeHandler(true)} + aria-label={label} + $css={css` + font-family: ${font}; + ${buttonCss}; + `} + className="--docs--drop-button" + > + {button} + + + + {children} + + + ); +}; diff --git a/src/frontend/apps/conversations/src/components/DropdownMenu.tsx b/src/frontend/apps/conversations/src/components/DropdownMenu.tsx new file mode 100644 index 0000000..8758588 --- /dev/null +++ b/src/frontend/apps/conversations/src/components/DropdownMenu.tsx @@ -0,0 +1,175 @@ +import { PropsWithChildren, useRef, useState } from 'react'; +import { css } from 'styled-components'; + +import { Box, BoxButton, BoxProps, DropButton, Icon, Text } from '@/components'; +import { useCunninghamTheme } from '@/cunningham'; + +export type DropdownMenuOption = { + icon?: string; + label: string; + testId?: string; + callback?: () => void | Promise; + danger?: boolean; + isSelected?: boolean; + disabled?: boolean; + show?: boolean; +}; + +export type DropdownMenuProps = { + options: DropdownMenuOption[]; + showArrow?: boolean; + label?: string; + arrowCss?: BoxProps['$css']; + buttonCss?: BoxProps['$css']; + disabled?: boolean; + topMessage?: string; +}; + +export const DropdownMenu = ({ + options, + children, + disabled = false, + showArrow = false, + arrowCss, + buttonCss, + label, + topMessage, +}: PropsWithChildren) => { + const { spacingsTokens, colorsTokens } = useCunninghamTheme(); + const [isOpen, setIsOpen] = useState(false); + const blockButtonRef = useRef(null); + + const onOpenChange = (isOpen: boolean) => { + setIsOpen(isOpen); + }; + + if (disabled) { + return children; + } + + return ( + + {children} + + + ) : ( + + {children} + + ) + } + > + + {topMessage && ( + + {topMessage} + + )} + {options.map((option, index) => { + if (option.show !== undefined && !option.show) { + return; + } + const isDisabled = option.disabled !== undefined && option.disabled; + return ( + { + event.preventDefault(); + event.stopPropagation(); + onOpenChange?.(false); + void option.callback?.(); + }} + key={option.label} + $align="center" + $justify="space-between" + $background={colorsTokens['greyscale-000']} + $color={colorsTokens['primary-600']} + $padding={{ vertical: 'xs', horizontal: 'base' }} + $width="100%" + $gap={spacingsTokens['base']} + $css={css` + border: none; + ${index === 0 && + css` + border-top-left-radius: 4px; + border-top-right-radius: 4px; + `} + ${index === options.length - 1 && + css` + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + `} + font-size: var(--c--theme--font--sizes--sm); + color: var(--c--theme--colors--greyscale-1000); + font-weight: 500; + cursor: ${isDisabled ? 'not-allowed' : 'pointer'}; + user-select: none; + + &:hover { + background-color: var(--c--theme--colors--greyscale-050); + } + `} + > + + {option.icon && ( + + )} + + {option.label} + + + {option.isSelected && ( + + )} + + ); + })} + + + ); +}; diff --git a/src/frontend/apps/conversations/src/components/Effect.tsx b/src/frontend/apps/conversations/src/components/Effect.tsx new file mode 100644 index 0000000..53cffc2 --- /dev/null +++ b/src/frontend/apps/conversations/src/components/Effect.tsx @@ -0,0 +1,11 @@ +export const showEffect = ` + transform: scaleY(1); + opacity: 1; + max-height: 150px; +`; + +export const hideEffect = ` + transform: scaleY(0); + opacity: 0; + max-height: 0; +`; diff --git a/src/frontend/apps/conversations/src/components/Icon.tsx b/src/frontend/apps/conversations/src/components/Icon.tsx new file mode 100644 index 0000000..c99d3f9 --- /dev/null +++ b/src/frontend/apps/conversations/src/components/Icon.tsx @@ -0,0 +1,43 @@ +import clsx from 'clsx'; +import { css } from 'styled-components'; + +import { Text, TextType } from '@/components'; + +type IconProps = TextType & { + iconName: string; + variant?: 'filled' | 'outlined'; +}; +export const Icon = ({ + iconName, + variant = 'outlined', + ...textProps +}: IconProps) => { + return ( + + {iconName} + + ); +}; + +type IconOptionsProps = TextType & { + isHorizontal?: boolean; +}; + +export const IconOptions = ({ isHorizontal, ...props }: IconOptionsProps) => { + return ( + + ); +}; diff --git a/src/frontend/apps/conversations/src/components/InfiniteScroll.tsx b/src/frontend/apps/conversations/src/components/InfiniteScroll.tsx new file mode 100644 index 0000000..5a0ee14 --- /dev/null +++ b/src/frontend/apps/conversations/src/components/InfiniteScroll.tsx @@ -0,0 +1,51 @@ +import { Button } from '@openfun/cunningham-react'; +import { PropsWithChildren } from 'react'; +import { useTranslation } from 'react-i18next'; +import { InView } from 'react-intersection-observer'; + +import { Box, BoxType, Icon } from '@/components'; + +interface InfiniteScrollProps extends BoxType { + hasMore: boolean; + isLoading: boolean; + next: () => void; + scrollContainer?: HTMLElement | null; + buttonLabel?: string; +} + +export const InfiniteScroll = ({ + children, + hasMore, + isLoading, + next, + buttonLabel, + ...boxProps +}: PropsWithChildren) => { + const { t } = useTranslation(); + const loadMore = (inView: boolean) => { + if (!inView || isLoading) { + return; + } + void next(); + }; + + return ( + + {children} + + {!isLoading && hasMore && ( + + )} + + + ); +}; diff --git a/src/frontend/apps/conversations/src/components/Link.tsx b/src/frontend/apps/conversations/src/components/Link.tsx new file mode 100644 index 0000000..0c90aef --- /dev/null +++ b/src/frontend/apps/conversations/src/components/Link.tsx @@ -0,0 +1,16 @@ +import Link from 'next/link'; +import styled, { RuleSet } from 'styled-components'; + +export interface LinkProps { + $css?: string | RuleSet; +} + +export const StyledLink = styled(Link)` + text-decoration: none; + color: #ffffff33; + &[aria-current='page'] { + color: #ffffff; + } + display: flex; + ${({ $css }) => $css && (typeof $css === 'string' ? `${$css};` : $css)} +`; diff --git a/src/frontend/apps/conversations/src/components/LoadMoreText.tsx b/src/frontend/apps/conversations/src/components/LoadMoreText.tsx new file mode 100644 index 0000000..4caa811 --- /dev/null +++ b/src/frontend/apps/conversations/src/components/LoadMoreText.tsx @@ -0,0 +1,36 @@ +import { useTranslation } from 'react-i18next'; + +import { Box } from './Box'; +import { Icon } from './Icon'; +import { Text } from './Text'; + +type LoadMoreTextProps = { + ['data-testid']?: string; +}; + +export const LoadMoreText = ({ + 'data-testid': dataTestId, +}: LoadMoreTextProps) => { + const { t } = useTranslation(); + + return ( + + + + {t('Load more')} + + + ); +}; diff --git a/src/frontend/apps/conversations/src/components/SideModal.tsx b/src/frontend/apps/conversations/src/components/SideModal.tsx new file mode 100644 index 0000000..d844304 --- /dev/null +++ b/src/frontend/apps/conversations/src/components/SideModal.tsx @@ -0,0 +1,57 @@ +import { Modal, ModalSize } from '@openfun/cunningham-react'; +import { ComponentPropsWithRef, PropsWithChildren } from 'react'; +import { createGlobalStyle } from 'styled-components'; + +interface SideModalStyleProps { + side: 'left' | 'right'; + width: string; + $css?: string; +} + +const SideModalStyle = createGlobalStyle` + @keyframes slidein { + from { + transform: translateX(100%); + } + + to { + transform: translateX(0%); + } + } + + & .c__modal{ + animation: slidein 0.7s; + + width: ${({ width }) => width}; + ${({ side }) => side === 'right' && 'left: auto;'}; + + .c__modal__scroller { + height: 100%; + display: flex; + flex-direction: column; + } + + ${({ $css }) => $css} + } +`; + +type SideModalType = Omit, 'size'>; + +type SideModalProps = SideModalType & Partial; + +export const SideModal = ({ + children, + side = 'right', + width = '35vw', + $css, + ...modalProps +}: PropsWithChildren) => { + return ( + <> + + + {children} + + + ); +}; diff --git a/src/frontend/apps/conversations/src/components/Text.tsx b/src/frontend/apps/conversations/src/components/Text.tsx new file mode 100644 index 0000000..83bf0b3 --- /dev/null +++ b/src/frontend/apps/conversations/src/components/Text.tsx @@ -0,0 +1,75 @@ +import { CSSProperties, ComponentPropsWithRef, forwardRef } from 'react'; +import styled from 'styled-components'; + +import { tokens } from '@/cunningham'; + +import { Box, BoxProps } from './Box'; + +const { sizes } = tokens.themes.default.theme.font; +type TextSizes = keyof typeof sizes; + +export interface TextProps extends BoxProps { + as?: 'p' | 'span' | 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; + $ellipsis?: boolean; + $weight?: CSSProperties['fontWeight']; + $textAlign?: CSSProperties['textAlign']; + $size?: TextSizes | (string & {}); + $theme?: + | 'primary' + | 'primary-text' + | 'secondary' + | 'secondary-text' + | 'info' + | 'success' + | 'warning' + | 'danger' + | 'greyscale'; + $variation?: + | 'text' + | '000' + | '100' + | '200' + | '300' + | '400' + | '500' + | '600' + | '700' + | '800' + | '900' + | '1000'; +} + +export type TextType = ComponentPropsWithRef; + +export const TextStyled = styled(Box)` + ${({ $textAlign }) => $textAlign && `text-align: ${$textAlign};`} + ${({ $weight }) => $weight && `font-weight: ${$weight};`} + ${({ $size }) => + $size && + `font-size: ${$size in sizes ? sizes[$size as TextSizes] : $size};`} + ${({ $theme, $variation }) => + `color: var(--c--theme--colors--${$theme}-${$variation});`} + ${({ $color }) => $color && `color: ${$color};`} + ${({ $ellipsis }) => + $ellipsis && + `white-space: nowrap; overflow: hidden; text-overflow: ellipsis;`} +`; + +const Text = forwardRef>( + ({ className, ...props }, ref) => { + return ( + + ); + }, +); + +Text.displayName = 'Text'; + +export { Text }; diff --git a/src/frontend/apps/conversations/src/components/TextErrors.tsx b/src/frontend/apps/conversations/src/components/TextErrors.tsx new file mode 100644 index 0000000..baf22a2 --- /dev/null +++ b/src/frontend/apps/conversations/src/components/TextErrors.tsx @@ -0,0 +1,64 @@ +import { Alert, VariantType } from '@openfun/cunningham-react'; +import { ReactNode } from 'react'; +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; + +import { Box, Text, TextType } from '@/components'; + +const AlertStyled = styled(Alert)` + & .c__button--tertiary:hover { + background-color: var(--c--theme--colors--greyscale-200); + } +`; + +interface TextErrorsProps extends TextType { + causes?: string[]; + defaultMessage?: string; + icon?: ReactNode; + canClose?: boolean; +} + +export const TextErrors = ({ + causes, + defaultMessage, + icon, + canClose = false, + ...textProps +}: TextErrorsProps) => { + const { t } = useTranslation(); + + return ( + + + {causes && + causes.map((cause, i) => ( + + {cause} + + ))} + + {!causes && ( + + {defaultMessage || t('Something bad happens, please retry.')} + + )} + + + ); +}; diff --git a/src/frontend/apps/conversations/src/components/__tests__/Box.spec.tsx b/src/frontend/apps/conversations/src/components/__tests__/Box.spec.tsx new file mode 100644 index 0000000..dc8ca85 --- /dev/null +++ b/src/frontend/apps/conversations/src/components/__tests__/Box.spec.tsx @@ -0,0 +1,51 @@ +import { render, screen } from '@testing-library/react'; + +import { Box } from '../Box'; + +describe('', () => { + it('has the padding from prop', () => { + const { unmount } = render(My Box); + + expect(screen.getByText('My Box')).toHaveStyle('padding: 10px'); + + unmount(); + + render( + + My Box + , + ); + + expect(screen.getByText('My Box')).toHaveStyle(` + padding-left: 2.5rem; + padding-right: 2.5rem; + padding-top: 3rem; + padding-bottom: 0.5rem;`); + }); + + it('has the margin from prop', () => { + const { unmount } = render(My Box); + expect(screen.getByText('My Box')).toHaveStyle('margin: 10px'); + + unmount(); + + render( + + My Box + , + ); + + expect(screen.getByText('My Box')).toHaveStyle(` + margin-left: auto; + margin-right: auto; + margin-top: 1.625rem; + margin-bottom: 100%;`); + }); +}); diff --git a/src/frontend/apps/conversations/src/components/index.ts b/src/frontend/apps/conversations/src/components/index.ts new file mode 100644 index 0000000..205b722 --- /dev/null +++ b/src/frontend/apps/conversations/src/components/index.ts @@ -0,0 +1,13 @@ +export * from './Box'; +export * from './BoxButton'; +export * from './Card'; +export * from './DropButton'; +export * from './DropdownMenu'; +export * from './Icon'; +export * from './InfiniteScroll'; +export * from './Link'; +export * from './LoadMoreText'; +export * from './SideModal'; +export * from './separators'; +export * from './Text'; +export * from './TextErrors'; diff --git a/src/frontend/apps/conversations/src/components/quick-search/QuickSearch.tsx b/src/frontend/apps/conversations/src/components/quick-search/QuickSearch.tsx new file mode 100644 index 0000000..2788792 --- /dev/null +++ b/src/frontend/apps/conversations/src/components/quick-search/QuickSearch.tsx @@ -0,0 +1,71 @@ +import { Command } from 'cmdk'; +import { ReactNode, useRef } from 'react'; + +import { hasChildrens } from '@/utils/children'; + +import { Box } from '../Box'; + +import { QuickSearchInput } from './QuickSearchInput'; +import { QuickSearchStyle } from './QuickSearchStyle'; + +export type QuickSearchAction = { + onSelect?: () => void; + content: ReactNode; +}; + +export type QuickSearchData = { + groupName: string; + elements: T[]; + emptyString?: string; + startActions?: QuickSearchAction[]; + endActions?: QuickSearchAction[]; + showWhenEmpty?: boolean; +}; + +export type QuickSearchProps = { + onFilter?: (str: string) => void; + inputValue?: string; + inputContent?: ReactNode; + showInput?: boolean; + loading?: boolean; + label?: string; + placeholder?: string; + children?: ReactNode; +}; + +export const QuickSearch = ({ + onFilter, + inputContent, + inputValue, + loading, + showInput = true, + label, + placeholder, + children, +}: QuickSearchProps) => { + const ref = useRef(null); + + return ( + <> + +
+ + {showInput && ( + + {inputContent} + + )} + + {children} + + +
+ + ); +}; diff --git a/src/frontend/apps/conversations/src/components/quick-search/QuickSearchGroup.tsx b/src/frontend/apps/conversations/src/components/quick-search/QuickSearchGroup.tsx new file mode 100644 index 0000000..33cffcf --- /dev/null +++ b/src/frontend/apps/conversations/src/components/quick-search/QuickSearchGroup.tsx @@ -0,0 +1,66 @@ +import { Command } from 'cmdk'; +import { ReactNode } from 'react'; + +import { Box } from '../Box'; + +import { QuickSearchData } from './QuickSearch'; +import { QuickSearchItem } from './QuickSearchItem'; + +type Props = { + group: QuickSearchData; + renderElement?: (element: T) => ReactNode; + onSelect?: (element: T) => void; +}; + +export const QuickSearchGroup = ({ + group, + onSelect, + renderElement, +}: Props) => { + return ( + + + {group.startActions?.map((action, index) => { + return ( + + {action.content} + + ); + })} + {group.elements.map((groupElement, index) => { + return ( + { + onSelect?.(groupElement); + }} + > + {renderElement?.(groupElement)} + + ); + })} + {group.endActions?.map((action, index) => { + return ( + + {action.content} + + ); + })} + {group.emptyString && group.elements.length === 0 && ( + {group.emptyString} + )} + + + ); +}; diff --git a/src/frontend/apps/conversations/src/components/quick-search/QuickSearchInput.tsx b/src/frontend/apps/conversations/src/components/quick-search/QuickSearchInput.tsx new file mode 100644 index 0000000..9ab52f5 --- /dev/null +++ b/src/frontend/apps/conversations/src/components/quick-search/QuickSearchInput.tsx @@ -0,0 +1,68 @@ +import { Loader } from '@openfun/cunningham-react'; +import { Command } from 'cmdk'; +import { ReactNode } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { HorizontalSeparator } from '@/components'; +import { useCunninghamTheme } from '@/cunningham'; + +import { Box } from '../Box'; +import { Icon } from '../Icon'; + +type Props = { + loading?: boolean; + inputValue?: string; + onFilter?: (str: string) => void; + placeholder?: string; + children?: ReactNode; + withSeparator?: boolean; +}; +export const QuickSearchInput = ({ + loading, + inputValue, + onFilter, + placeholder, + children, + withSeparator: separator = true, +}: Props) => { + const { t } = useTranslation(); + const { spacingsTokens } = useCunninghamTheme(); + + if (children) { + return ( + <> + {children} + {separator && } + + ); + } + + return ( + <> + + {!loading && } + {loading && ( +
+ +
+ )} + +
+ {separator && } + + ); +}; diff --git a/src/frontend/apps/conversations/src/components/quick-search/QuickSearchItem.tsx b/src/frontend/apps/conversations/src/components/quick-search/QuickSearchItem.tsx new file mode 100644 index 0000000..b3d7e07 --- /dev/null +++ b/src/frontend/apps/conversations/src/components/quick-search/QuickSearchItem.tsx @@ -0,0 +1,18 @@ +import { Command } from 'cmdk'; +import { PropsWithChildren } from 'react'; + +type Props = { + onSelect?: (value: string) => void; + id?: string; +}; +export const QuickSearchItem = ({ + children, + onSelect, + id, +}: PropsWithChildren) => { + return ( + + {children} + + ); +}; diff --git a/src/frontend/apps/conversations/src/components/quick-search/QuickSearchItemContent.tsx b/src/frontend/apps/conversations/src/components/quick-search/QuickSearchItemContent.tsx new file mode 100644 index 0000000..09330bd --- /dev/null +++ b/src/frontend/apps/conversations/src/components/quick-search/QuickSearchItemContent.tsx @@ -0,0 +1,51 @@ +import { ReactNode } from 'react'; + +import { useCunninghamTheme } from '@/cunningham'; +import { useResponsiveStore } from '@/stores'; + +import { Box } from '../Box'; + +export type QuickSearchItemContentProps = { + alwaysShowRight?: boolean; + left: ReactNode; + right?: ReactNode; +}; + +export const QuickSearchItemContent = ({ + alwaysShowRight = false, + left, + right, +}: QuickSearchItemContentProps) => { + const { spacingsTokens } = useCunninghamTheme(); + + const { isDesktop } = useResponsiveStore(); + + return ( + + + {left} + + + {isDesktop && right && ( + + {right} + + )} + + ); +}; diff --git a/src/frontend/apps/conversations/src/components/quick-search/QuickSearchStyle.tsx b/src/frontend/apps/conversations/src/components/quick-search/QuickSearchStyle.tsx new file mode 100644 index 0000000..b6fa0ad --- /dev/null +++ b/src/frontend/apps/conversations/src/components/quick-search/QuickSearchStyle.tsx @@ -0,0 +1,139 @@ +import { createGlobalStyle } from 'styled-components'; + +export const QuickSearchStyle = createGlobalStyle` + .quick-search-container { + [cmdk-root] { + width: 100%; + background: #ffffff; + border-radius: 12px; + overflow: hidden; + transition: transform 100ms ease; + outline: none; + } + + [cmdk-input] { + border: none; + width: 100%; + font-size: 17px; + padding: 8px; + background: white; + outline: none; + color: var(--c--theme--colors--greyscale-1000); + border-radius: 0; + + &::placeholder { + color: var(--c--theme--colors--greyscale-500); + } + } + + + + [cmdk-item] { + content-visibility: auto; + cursor: pointer; + border-radius: var(--c--theme--spacings--xs); + font-size: 14px; + display: flex; + align-items: center; + gap: 8px; + user-select: none; + will-change: background, color; + transition: all 150ms ease; + transition-property: none; + + .show-right-on-focus { + opacity: 0; + } + + &:hover, + &[data-selected='true'] { + background: var(--c--theme--colors--greyscale-100); + .show-right-on-focus { + opacity: 1; + } + } + + &[data-disabled='true'] { + color: var(--c--theme--colors--greyscale-500); + cursor: not-allowed; + } + + & + [cmdk-item] { + margin-top: 4px; + } + } + + [cmdk-list] { + + padding: 0 var(--c--theme--spacings--base) var(--c--theme--spacings--base) + var(--c--theme--spacings--base); + + flex:1; + overflow-y: auto; + overscroll-behavior: contain; + } + + [cmdk-vercel-shortcuts] { + display: flex; + margin-left: auto; + gap: 8px; + + kbd { + font-size: 12px; + min-width: 20px; + padding: 4px; + height: 20px; + border-radius: 4px; + color: white; + background: var(--c--theme--colors--greyscale-500); + display: inline-flex; + align-items: center; + justify-content: center; + text-transform: uppercase; + } + } + + [cmdk-separator] { + height: 1px; + width: 100%; + background: var(--c--theme--colors--greyscale-500); + margin: 4px 0; + } + + *:not([hidden]) + [cmdk-group] { + margin-top: 8px; + } + + [cmdk-group-heading] { + user-select: none; + font-size: var(--c--theme--font--sizes--sm); + color: var(--c--theme--colors--greyscale-700); + font-weight: bold; + + display: flex; + align-items: center; + margin-bottom: var(--c--theme--spacings--xs); + } + + [cmdk-empty] { + } +} + +.c__modal__scroller:has(.quick-search-container), +.c__modal__scroller:has(.noPadding) { + padding: 0 !important; + + .c__modal__close .c__button { + right: 5px; + top: 5px; + padding: 1.5rem 1rem; + } + + .c__modal__title { + font-size: var(--c--theme--font--sizes--xs); + + padding: var(--c--theme--spacings--base); + margin-bottom: 0; + } +} +`; diff --git a/src/frontend/apps/conversations/src/components/quick-search/index.ts b/src/frontend/apps/conversations/src/components/quick-search/index.ts new file mode 100644 index 0000000..140e592 --- /dev/null +++ b/src/frontend/apps/conversations/src/components/quick-search/index.ts @@ -0,0 +1,4 @@ +export * from './QuickSearch'; +export * from './QuickSearchGroup'; +export * from './QuickSearchItem'; +export * from './QuickSearchItemContent'; diff --git a/src/frontend/apps/conversations/src/components/separators/HorizontalSeparator.tsx b/src/frontend/apps/conversations/src/components/separators/HorizontalSeparator.tsx new file mode 100644 index 0000000..f8ab9bd --- /dev/null +++ b/src/frontend/apps/conversations/src/components/separators/HorizontalSeparator.tsx @@ -0,0 +1,34 @@ +import { useCunninghamTheme } from '@/cunningham'; + +import { Box } from '../Box'; + +export enum SeparatorVariant { + LIGHT = 'light', + DARK = 'dark', +} + +type Props = { + variant?: SeparatorVariant; + $withPadding?: boolean; +}; + +export const HorizontalSeparator = ({ + variant = SeparatorVariant.LIGHT, + $withPadding = true, +}: Props) => { + const { colorsTokens } = useCunninghamTheme(); + + return ( + + ); +}; diff --git a/src/frontend/apps/conversations/src/components/separators/SeparatedSection.tsx b/src/frontend/apps/conversations/src/components/separators/SeparatedSection.tsx new file mode 100644 index 0000000..0411f5b --- /dev/null +++ b/src/frontend/apps/conversations/src/components/separators/SeparatedSection.tsx @@ -0,0 +1,31 @@ +import { PropsWithChildren } from 'react'; +import { css } from 'styled-components'; + +import { useCunninghamTheme } from '@/cunningham'; + +import { Box } from '../Box'; + +type Props = { + showSeparator?: boolean; +}; + +export const SeparatedSection = ({ + showSeparator = true, + children, +}: PropsWithChildren) => { + const { colorsTokens, spacingsTokens } = useCunninghamTheme(); + return ( + + {children} + + ); +}; diff --git a/src/frontend/apps/conversations/src/components/separators/index.ts b/src/frontend/apps/conversations/src/components/separators/index.ts new file mode 100644 index 0000000..f8d2100 --- /dev/null +++ b/src/frontend/apps/conversations/src/components/separators/index.ts @@ -0,0 +1,2 @@ +export * from './HorizontalSeparator'; +export * from './SeparatedSection'; diff --git a/src/frontend/apps/conversations/src/core/AppProvider.tsx b/src/frontend/apps/conversations/src/core/AppProvider.tsx new file mode 100644 index 0000000..03ce509 --- /dev/null +++ b/src/frontend/apps/conversations/src/core/AppProvider.tsx @@ -0,0 +1,71 @@ +import { CunninghamProvider } from '@openfun/cunningham-react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { useRouter } from 'next/router'; +import { useEffect } from 'react'; + +import { useCunninghamTheme } from '@/cunningham'; +import { Auth, KEY_AUTH, setAuthUrl } from '@/features/auth'; +import { useResponsiveStore } from '@/stores/'; + +import { ConfigProvider } from './config/'; + +/** + * QueryClient: + * - defaultOptions: + * - staleTime: + * - global cache duration - we decided 3 minutes + * - It can be overridden to each query + */ +const defaultOptions = { + queries: { + staleTime: 1000 * 60 * 3, + retry: 1, + }, +}; +const queryClient = new QueryClient({ + defaultOptions, +}); + +export function AppProvider({ children }: { children: React.ReactNode }) { + const { theme } = useCunninghamTheme(); + const { replace } = useRouter(); + + const initializeResizeListener = useResponsiveStore( + (state) => state.initializeResizeListener, + ); + + useEffect(() => { + return initializeResizeListener(); + }, [initializeResizeListener]); + + useEffect(() => { + queryClient.setDefaultOptions({ + ...defaultOptions, + mutations: { + onError: (error) => { + if ( + error instanceof Error && + 'status' in error && + error.status === 401 + ) { + void queryClient.resetQueries({ + queryKey: [KEY_AUTH], + }); + setAuthUrl(); + void replace(`/401`); + } + }, + }, + }); + }, [replace]); + + return ( + + + + {children} + + + + ); +} diff --git a/src/frontend/apps/conversations/src/core/api/useUserUpdate.ts b/src/frontend/apps/conversations/src/core/api/useUserUpdate.ts new file mode 100644 index 0000000..6798edf --- /dev/null +++ b/src/frontend/apps/conversations/src/core/api/useUserUpdate.ts @@ -0,0 +1,48 @@ +import { + UseMutationResult, + useMutation, + useQueryClient, +} from '@tanstack/react-query'; + +import { APIError, errorCauses, fetchAPI } from '@/api'; +import { User } from '@/features/auth/api/types'; +import { KEY_AUTH } from '@/features/auth/api/useAuthQuery'; + +type UserUpdateRequest = Partial; + +async function updateUser(userUpdateData: UserUpdateRequest): Promise { + const response = await fetchAPI(`users/${userUpdateData.id}/`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(userUpdateData), + }); + if (!response.ok) { + throw new APIError( + `Failed to update the user`, + await errorCauses(response, userUpdateData), + ); + } + return response.json() as Promise; +} + +export const useUserUpdate = (): UseMutationResult< + User, + APIError, + UserUpdateRequest +> => { + const queryClient = useQueryClient(); + + const mutationResult = useMutation({ + mutationFn: updateUser, + onSuccess: () => { + void queryClient.invalidateQueries({ queryKey: [KEY_AUTH] }); + }, + onError: (error) => { + console.error('Error updating user', error); + }, + }); + + return mutationResult; +}; diff --git a/src/frontend/apps/conversations/src/core/config/ConfigProvider.tsx b/src/frontend/apps/conversations/src/core/config/ConfigProvider.tsx new file mode 100644 index 0000000..6694f6b --- /dev/null +++ b/src/frontend/apps/conversations/src/core/config/ConfigProvider.tsx @@ -0,0 +1,97 @@ +import { Loader } from '@openfun/cunningham-react'; +import Head from 'next/head'; +import { PropsWithChildren, useEffect, useRef } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { Box } from '@/components'; +import { useCunninghamTheme } from '@/cunningham'; +import { useAuthQuery } from '@/features/auth'; +import { + useCustomTranslations, + useSynchronizedLanguage, +} from '@/features/language'; +import { useAnalytics } from '@/libs'; +import { CrispProvider, PostHogAnalytic } from '@/services'; +import { useSentryStore } from '@/stores/useSentryStore'; + +import { useConfig } from './api/useConfig'; + +export const ConfigProvider = ({ children }: PropsWithChildren) => { + const { data: conf } = useConfig(); + const { data: user } = useAuthQuery(); + const { setSentry } = useSentryStore(); + const { setTheme } = useCunninghamTheme(); + const { changeLanguageSynchronized } = useSynchronizedLanguage(); + const { customizeTranslations } = useCustomTranslations(); + const { AnalyticsProvider } = useAnalytics(); + const { i18n } = useTranslation(); + const languageSynchronized = useRef(false); + + useEffect(() => { + if (!user || languageSynchronized.current) { + return; + } + + const targetLanguage = + user?.language ?? i18n.resolvedLanguage ?? i18n.language; + + void changeLanguageSynchronized(targetLanguage, user).then(() => { + languageSynchronized.current = true; + }); + }, [user, i18n.resolvedLanguage, i18n.language, changeLanguageSynchronized]); + + useEffect(() => { + if (!conf?.theme_customization?.translations) { + return; + } + + customizeTranslations(conf.theme_customization.translations); + }, [conf?.theme_customization?.translations, customizeTranslations]); + + useEffect(() => { + if (!conf?.SENTRY_DSN) { + return; + } + + setSentry(conf.SENTRY_DSN, conf.ENVIRONMENT); + }, [conf?.SENTRY_DSN, conf?.ENVIRONMENT, setSentry]); + + useEffect(() => { + if (!conf?.FRONTEND_THEME) { + return; + } + + setTheme(conf.FRONTEND_THEME); + }, [conf?.FRONTEND_THEME, setTheme]); + + useEffect(() => { + if (!conf?.POSTHOG_KEY) { + return; + } + + new PostHogAnalytic(conf.POSTHOG_KEY); + }, [conf?.POSTHOG_KEY]); + + if (!conf) { + return ( + + + + ); + } + + return ( + <> + {conf?.FRONTEND_CSS_URL && ( + + + + )} + + + {children} + + + + ); +}; diff --git a/src/frontend/apps/conversations/src/core/config/api/index.ts b/src/frontend/apps/conversations/src/core/config/api/index.ts new file mode 100644 index 0000000..4852f57 --- /dev/null +++ b/src/frontend/apps/conversations/src/core/config/api/index.ts @@ -0,0 +1 @@ +export * from './useConfig'; diff --git a/src/frontend/apps/conversations/src/core/config/api/useConfig.tsx b/src/frontend/apps/conversations/src/core/config/api/useConfig.tsx new file mode 100644 index 0000000..f82ca00 --- /dev/null +++ b/src/frontend/apps/conversations/src/core/config/api/useConfig.tsx @@ -0,0 +1,69 @@ +import { useQuery } from '@tanstack/react-query'; +import { Resource } from 'i18next'; + +import { APIError, errorCauses, fetchAPI } from '@/api'; +import { Theme } from '@/cunningham/'; +import { FooterType } from '@/features/footer'; +import { PostHogConf } from '@/services'; + +interface ThemeCustomization { + footer?: FooterType; + translations?: Resource; +} + +export interface ConfigResponse { + CRISP_WEBSITE_ID?: string; + ENVIRONMENT: string; + FRONTEND_CSS_URL?: string; + FRONTEND_HOMEPAGE_FEATURE_ENABLED?: boolean; + FRONTEND_THEME?: Theme; + LANGUAGES: [string, string][]; + LANGUAGE_CODE: string; + MEDIA_BASE_URL?: string; + POSTHOG_KEY?: PostHogConf; + SENTRY_DSN?: string; + theme_customization?: ThemeCustomization; +} + +const LOCAL_STORAGE_KEY = 'conversations_config'; + +function getCachedTranslation() { + try { + const jsonString = localStorage.getItem(LOCAL_STORAGE_KEY); + return jsonString ? (JSON.parse(jsonString) as ConfigResponse) : undefined; + } catch { + return undefined; + } +} + +function setCachedTranslation(translations: ConfigResponse) { + localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(translations)); +} + +export const getConfig = async (): Promise => { + const response = await fetchAPI(`config/`); + + if (!response.ok) { + throw new APIError('Failed to get the doc', await errorCauses(response)); + } + + const config = response.json() as Promise; + setCachedTranslation(await config); + + return config; +}; + +export const KEY_CONFIG = 'config'; + +export function useConfig() { + const cachedData = getCachedTranslation(); + const oneHour = 1000 * 60 * 60; + + return useQuery({ + queryKey: [KEY_CONFIG], + queryFn: () => getConfig(), + initialData: cachedData, + staleTime: oneHour, + initialDataUpdatedAt: Date.now() - oneHour, // Force initial data to be considered stale + }); +} diff --git a/src/frontend/apps/conversations/src/core/config/hooks/index.ts b/src/frontend/apps/conversations/src/core/config/hooks/index.ts new file mode 100644 index 0000000..41d6d8d --- /dev/null +++ b/src/frontend/apps/conversations/src/core/config/hooks/index.ts @@ -0,0 +1 @@ +export * from './useMediaUrl'; diff --git a/src/frontend/apps/conversations/src/core/config/hooks/useMediaUrl.tsx b/src/frontend/apps/conversations/src/core/config/hooks/useMediaUrl.tsx new file mode 100644 index 0000000..1fcb5ec --- /dev/null +++ b/src/frontend/apps/conversations/src/core/config/hooks/useMediaUrl.tsx @@ -0,0 +1,10 @@ +import { useConfig } from '../api'; + +export const useMediaUrl = () => { + const { data: conf } = useConfig(); + + return ( + conf?.MEDIA_BASE_URL || + (typeof window !== 'undefined' ? window.location.origin : '') + ); +}; diff --git a/src/frontend/apps/conversations/src/core/config/index.ts b/src/frontend/apps/conversations/src/core/config/index.ts new file mode 100644 index 0000000..e786d39 --- /dev/null +++ b/src/frontend/apps/conversations/src/core/config/index.ts @@ -0,0 +1,3 @@ +export * from './api/'; +export * from './ConfigProvider'; +export * from './hooks'; diff --git a/src/frontend/apps/conversations/src/core/index.ts b/src/frontend/apps/conversations/src/core/index.ts new file mode 100644 index 0000000..bc6b139 --- /dev/null +++ b/src/frontend/apps/conversations/src/core/index.ts @@ -0,0 +1,3 @@ +export * from './AppProvider'; +export * from './config'; +export * from './settings'; diff --git a/src/frontend/apps/conversations/src/core/settings.ts b/src/frontend/apps/conversations/src/core/settings.ts new file mode 100644 index 0000000..54558b2 --- /dev/null +++ b/src/frontend/apps/conversations/src/core/settings.ts @@ -0,0 +1,2 @@ +export const productName = + process.env.NEXT_PUBLIC_PRODUCT_NAME || 'Conversations'; diff --git a/src/frontend/apps/conversations/src/cunningham/__tests__/useCunninghamTheme.spec.tsx b/src/frontend/apps/conversations/src/cunningham/__tests__/useCunninghamTheme.spec.tsx new file mode 100644 index 0000000..208d894 --- /dev/null +++ b/src/frontend/apps/conversations/src/cunningham/__tests__/useCunninghamTheme.spec.tsx @@ -0,0 +1,16 @@ +import { useCunninghamTheme } from '../useCunninghamTheme'; + +describe('', () => { + it('has the logo correctly set', () => { + expect(useCunninghamTheme.getState().themeTokens.logo?.src).toBe(''); + + // Change theme + useCunninghamTheme.getState().setTheme('dsfr'); + + const { themeTokens } = useCunninghamTheme.getState(); + const logo = themeTokens.logo; + expect(logo?.src).toBe('/assets/logo-gouv.svg'); + expect(logo?.widthHeader).toBe('110px'); + expect(logo?.widthFooter).toBe('220px'); + }); +}); diff --git a/src/frontend/apps/conversations/src/cunningham/cunningham-style.css b/src/frontend/apps/conversations/src/cunningham/cunningham-style.css new file mode 100644 index 0000000..8087717 --- /dev/null +++ b/src/frontend/apps/conversations/src/cunningham/cunningham-style.css @@ -0,0 +1,78 @@ +@import url('@gouvfr-lasuite/ui-kit/style'); +@import url('./cunningham-tokens.css'); + +:root { + /** + * Input + */ + --c--components--forms-input--border-radius--hover: var( + --c--components--forms-input--border-radius + ); + --c--components--forms-input--border-radius--focus: var( + --c--components--forms-input--border-radius + ); + --c--components--forms-input--border-color--hover: var( + --c--components--forms-input--border-color + ); + + /** + * Datepicker + **/ + --c--components--forms-datepicker--border-color--hover: var( + --c--components--forms-datepicker--border-color + ); + + /** + * Select + **/ + --c--components--forms-select--value-color--disabled: var( + --c--theme--colors--greyscale-400 + ); + + /** + * Button + **/ + --c--components--button--border-radius--active: var( + --c--components--button--border-radius + ); +} + +/** + * Tooltip +*/ +.c__tooltip { + padding: 4px 6px; +} + +/** + * Image System +*/ +.c__image-system-filter { + filter: var(--c--components--image-system-filter); +} + +@font-face { + font-family: Inter; + font-style: italic; + font-weight: 100 900; + font-display: swap; + src: url('https://fonts.gstatic.com/s/inter/v18/UcCm3FwrK3iLTcvnUwQT9g.woff2') + format('woff2'); + unicode-range: + U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, + U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, + U+2215, U+FEFF, U+FFFD; +} + +@font-face { + font-family: Inter; + font-style: normal; + font-weight: 100 900; + font-display: swap; + src: url('https://fonts.gstatic.com/s/inter/v18/UcCo3FwrK3iLTcviYwY.woff2') + format('woff2'); + unicode-range: + U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, + U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, + U+2215, U+FEFF, U+FFFD; +} diff --git a/src/frontend/apps/conversations/src/cunningham/cunningham-tokens.css b/src/frontend/apps/conversations/src/cunningham/cunningham-tokens.css new file mode 100644 index 0000000..2fdb932 --- /dev/null +++ b/src/frontend/apps/conversations/src/cunningham/cunningham-tokens.css @@ -0,0 +1,2767 @@ +:root { + --c--theme--colors--secondary-text: #fff; + --c--theme--colors--secondary-100: #fee9ea; + --c--theme--colors--secondary-200: #fedfdf; + --c--theme--colors--secondary-300: #fdbfbf; + --c--theme--colors--secondary-400: #e1020f; + --c--theme--colors--secondary-500: #c91a1f; + --c--theme--colors--secondary-600: #5e2b2b; + --c--theme--colors--secondary-700: #3b2424; + --c--theme--colors--secondary-800: #341f1f; + --c--theme--colors--secondary-900: #2b1919; + --c--theme--colors--info-text: #0078f3; + --c--theme--colors--info-100: #e8edff; + --c--theme--colors--info-200: #dde5ff; + --c--theme--colors--info-300: #bccdff; + --c--theme--colors--info-400: #518fff; + --c--theme--colors--info-500: #0078f3; + --c--theme--colors--info-600: #0063cb; + --c--theme--colors--info-700: #273961; + --c--theme--colors--info-800: #222a3f; + --c--theme--colors--info-900: #1d2437; + --c--theme--colors--greyscale-100: #eee; + --c--theme--colors--greyscale-200: #e5e5e5; + --c--theme--colors--greyscale-300: #cecece; + --c--theme--colors--greyscale-400: #929292; + --c--theme--colors--greyscale-500: #7c7c7c; + --c--theme--colors--greyscale-600: #666; + --c--theme--colors--greyscale-700: #3a3a3a; + --c--theme--colors--greyscale-800: #2a2a2a; + --c--theme--colors--greyscale-900: #242424; + --c--theme--colors--greyscale-000: #fff; + --c--theme--colors--primary-100: #ececfe; + --c--theme--colors--primary-200: #e3e3fd; + --c--theme--colors--primary-300: #cacafb; + --c--theme--colors--primary-400: #8585f6; + --c--theme--colors--primary-500: #6a6af4; + --c--theme--colors--primary-600: #313178; + --c--theme--colors--primary-700: #272747; + --c--theme--colors--primary-800: #000091; + --c--theme--colors--primary-900: #21213f; + --c--theme--colors--success-100: #dffee6; + --c--theme--colors--success-200: #b8fec9; + --c--theme--colors--success-300: #88fdaa; + --c--theme--colors--success-400: #3bea7e; + --c--theme--colors--success-500: #1f8d49; + --c--theme--colors--success-600: #18753c; + --c--theme--colors--success-700: #204129; + --c--theme--colors--success-800: #1e2e22; + --c--theme--colors--success-900: #19281d; + --c--theme--colors--warning-100: #fff4f3; + --c--theme--colors--warning-200: #ffe9e6; + --c--theme--colors--warning-300: #ffded9; + --c--theme--colors--warning-400: #ffbeb4; + --c--theme--colors--warning-500: #d64d00; + --c--theme--colors--warning-600: #b34000; + --c--theme--colors--warning-700: #5e2c21; + --c--theme--colors--warning-800: #3e241e; + --c--theme--colors--warning-900: #361e19; + --c--theme--colors--danger-100: #ffe9e9; + --c--theme--colors--danger-200: #fdd; + --c--theme--colors--danger-300: #ffbdbd; + --c--theme--colors--danger-400: #ff5655; + --c--theme--colors--danger-500: #f60700; + --c--theme--colors--danger-600: #ce0500; + --c--theme--colors--danger-700: #642626; + --c--theme--colors--danger-800: #412121; + --c--theme--colors--danger-900: #391c1c; + --c--theme--colors--primary-text: #000091; + --c--theme--colors--success-text: #1f8d49; + --c--theme--colors--warning-text: #d64d00; + --c--theme--colors--danger-text: #fff; + --c--theme--colors--primary-050: #f5f5fe; + --c--theme--colors--primary-150: #f4f4fd; + --c--theme--colors--greyscale-text: #303c4b; + --c--theme--colors--greyscale-050: #f6f6f6; + --c--theme--colors--greyscale-250: #ddd; + --c--theme--colors--greyscale-350: #ddd; + --c--theme--colors--greyscale-750: #353535; + --c--theme--colors--greyscale-950: #1e1e1e; + --c--theme--colors--greyscale-1000: #161616; + --c--theme--colors--danger-050: #fff4f4; + --c--theme--colors--blue-500: #417dc4; + --c--theme--colors--brown-500: #bd987a; + --c--theme--colors--cyan-500: #009099; + --c--theme--colors--gold-500: #c3992a; + --c--theme--colors--green-500: #00a95f; + --c--theme--colors--olive-500: #68a532; + --c--theme--colors--orange-500: #e4794a; + --c--theme--colors--purple-500: #a558a0; + --c--theme--colors--red-500: #e1000f; + --c--theme--colors--yellow-500: #b7a73f; + --c--theme--colors--rose-500: #e18b76; + --c--theme--colors--primary-action: #1212ff; + --c--theme--colors--primary-bg: #fafafa; + --c--theme--colors--primary-focus: #0a76f6; + --c--theme--colors--secondary-icon: var(--c--theme--colors--primary-text); + --c--theme--colors--blue-400: #7ab1e8; + --c--theme--colors--blue-600: #3558a2; + --c--theme--colors--brown-400: #e6be92; + --c--theme--colors--brown-600: #745b47; + --c--theme--colors--cyan-400: #34bab5; + --c--theme--colors--cyan-600: #006a6f; + --c--theme--colors--gold-400: #ffca00; + --c--theme--colors--gold-600: #695240; + --c--theme--colors--green-400: #34cb6a; + --c--theme--colors--green-600: #297254; + --c--theme--colors--olive-400: #99c221; + --c--theme--colors--olive-600: #447049; + --c--theme--colors--orange-400: #ff732c; + --c--theme--colors--orange-600: #755348; + --c--theme--colors--pink-400: #ffb7ae; + --c--theme--colors--pink-500: #e18b76; + --c--theme--colors--pink-600: #8d533e; + --c--theme--colors--purple-400: #ce70cc; + --c--theme--colors--purple-600: #6e445a; + --c--theme--colors--yellow-400: #d8c634; + --c--theme--colors--yellow-600: #66673d; + --c--theme--font--sizes--h1: 2rem; + --c--theme--font--sizes--h2: 1.75rem; + --c--theme--font--sizes--h3: 1.5rem; + --c--theme--font--sizes--h4: 1.375rem; + --c--theme--font--sizes--h5: 1.25rem; + --c--theme--font--sizes--h6: 1.125rem; + --c--theme--font--sizes--l: 1rem; + --c--theme--font--sizes--m: 0.8125rem; + --c--theme--font--sizes--s: 0.75rem; + --c--theme--font--sizes--xs: 0.75rem; + --c--theme--font--sizes--sm: 0.875rem; + --c--theme--font--sizes--md: 1rem; + --c--theme--font--sizes--lg: 1.125rem; + --c--theme--font--sizes--ml: 0.938rem; + --c--theme--font--sizes--xl: 1.25rem; + --c--theme--font--sizes--t: 0.6875rem; + --c--theme--font--sizes--xl-alt: 5rem; + --c--theme--font--sizes--lg-alt: 4.5rem; + --c--theme--font--sizes--md-alt: 4rem; + --c--theme--font--sizes--sm-alt: 3.5rem; + --c--theme--font--sizes--xs-alt: 3rem; + --c--theme--font--weights--thin: 100; + --c--theme--font--weights--light: 300; + --c--theme--font--weights--regular: 400; + --c--theme--font--weights--medium: 500; + --c--theme--font--weights--bold: 600; + --c--theme--font--weights--extrabold: 800; + --c--theme--font--weights--black: 900; + --c--theme--font--families--base: marianne; + --c--theme--font--families--accent: marianne; + --c--theme--font--letterspacings--h1: normal; + --c--theme--font--letterspacings--h2: normal; + --c--theme--font--letterspacings--h3: normal; + --c--theme--font--letterspacings--h4: normal; + --c--theme--font--letterspacings--h5: 1px; + --c--theme--font--letterspacings--h6: normal; + --c--theme--font--letterspacings--l: normal; + --c--theme--font--letterspacings--m: normal; + --c--theme--font--letterspacings--s: normal; + --c--theme--spacings--0: 0; + --c--theme--spacings--xl: 2.5rem; + --c--theme--spacings--l: 3rem; + --c--theme--spacings--b: 1.625rem; + --c--theme--spacings--s: 1rem; + --c--theme--spacings--t: 0.5rem; + --c--theme--spacings--st: 0.25rem; + --c--theme--spacings--none: 0; + --c--theme--spacings--auto: auto; + --c--theme--spacings--bx: 2.2rem; + --c--theme--spacings--full: 100%; + --c--theme--spacings--4xs: 0.125rem; + --c--theme--spacings--3xs: 0.25rem; + --c--theme--spacings--2xs: 0.375rem; + --c--theme--spacings--xs: 0.5rem; + --c--theme--spacings--sm: 0.75rem; + --c--theme--spacings--base: 1rem; + --c--theme--spacings--md: 1.5rem; + --c--theme--spacings--lg: 2rem; + --c--theme--spacings--xxl: 3rem; + --c--theme--spacings--xxxl: 3.5rem; + --c--theme--spacings--4xl: 4rem; + --c--theme--spacings--5xl: 4.5rem; + --c--theme--spacings--6xl: 6rem; + --c--theme--spacings--7xl: 7.5rem; + --c--theme--transitions--ease-in: cubic-bezier(0.32, 0, 0.67, 0); + --c--theme--transitions--ease-out: cubic-bezier(0.33, 1, 0.68, 1); + --c--theme--transitions--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); + --c--theme--transitions--duration: 250ms; + --c--theme--breakpoints--xs: 480px; + --c--theme--breakpoints--sm: 576px; + --c--theme--breakpoints--md: 768px; + --c--theme--breakpoints--lg: 992px; + --c--theme--breakpoints--xl: 1200px; + --c--theme--breakpoints--xxl: 1400px; + --c--theme--breakpoints--xxs: 320px; + --c--theme--breakpoints--mobile: 768px; + --c--theme--breakpoints--tablet: 1024px; + --c--theme--logo--src: ; + --c--theme--logo--alt: ; + --c--theme--logo--widthheader: ; + --c--theme--logo--widthfooter: ; + --c--components--modal--width-small: 342px; + --c--components--tooltip--padding: 4px 8px; + --c--components--tooltip--background-color: var( + --c--theme--colors--greyscale-1000 + ); + --c--components--button--medium-height: 40px; + --c--components--button--medium-text-height: 40px; + --c--components--button--border-radius: 4px; + --c--components--button--small-height: 26px; + --c--components--button--primary--background--color: var( + --c--theme--colors--primary-text + ); + --c--components--button--primary--background--color-hover: #1212ff; + --c--components--button--primary--background--color-active: #2323ff; + --c--components--button--primary--background--color-disabled: var( + --c--theme--colors--greyscale-100 + ); + --c--components--button--primary--color: #fff; + --c--components--button--primary--color-hover: #fff; + --c--components--button--primary--color-active: #fff; + --c--components--button--primary--color-focus-visible: #fff; + --c--components--button--primary--disabled: var( + --c--theme--colors--greyscale-500 + ); + --c--components--button--primary-text--background--color: var( + --c--theme--colors--primary-text + ); + --c--components--button--primary-text--background--color-hover: var( + --c--theme--colors--greyscale-100 + ); + --c--components--button--primary-text--background--color-active: var( + --c--theme--colors--primary-100 + ); + --c--components--button--primary-text--background--color-focus-visible: #fff; + --c--components--button--primary-text--background--color-disabled: var( + --c--theme--colors--greyscale-000 + ); + --c--components--button--primary-text--color: var( + --c--theme--colors--primary-800 + ); + --c--components--button--primary-text--color-hover: var( + --c--theme--colors--primary-800 + ); + --c--components--button--primary-text--disabled: var( + --c--theme--colors--greyscale-400 + ); + --c--components--button--secondary--background--color-hover: #f6f6f6; + --c--components--button--secondary--background--color-active: #ededed; + --c--components--button--secondary--background--color-focus-visible: var( + --c--theme--colors--greyscale-000 + ); + --c--components--button--secondary--background--disabled: var( + --c--theme--colors--greyscale-000 + ); + --c--components--button--secondary--color: var( + --c--theme--colors--primary-800 + ); + --c--components--button--secondary--border--color: var( + --c--theme--colors--greyscale-300 + ); + --c--components--button--secondary--border--color-hover: var( + --c--theme--colors--greyscale-300 + ); + --c--components--button--secondary--border--color-disabled: var( + --c--theme--colors--greyscale-300 + ); + --c--components--button--secondary--disabled: var( + --c--theme--colors--greyscale-400 + ); + --c--components--button--tertiary--background--color: var( + --c--theme--colors--primary-100 + ); + --c--components--button--tertiary--background--color-focus-visible: var( + --c--theme--colors--primary-100 + ); + --c--components--button--tertiary--background--color-hover: var( + --c--theme--colors--primary-300 + ); + --c--components--button--tertiary--background--color-active: var( + --c--theme--colors--primary-300 + ); + --c--components--button--tertiary--background--disabled: var( + --c--theme--colors--primary-050 + ); + --c--components--button--tertiary--color: var( + --c--theme--colors--primary-800 + ); + --c--components--button--tertiary--disabled: var( + --c--theme--colors--primary-300 + ); + --c--components--button--tertiary-text--background--color-hover: var( + --c--theme--colors--greyscale-100 + ); + --c--components--button--tertiary-text--color-hover: var( + --c--theme--colors--primary-text + ); + --c--components--button--tertiary-text--color: var( + --c--theme--colors--primary-600 + ); + --c--components--button--danger--color-hover: white; + --c--components--button--danger--background--color: var( + --c--theme--colors--danger-600 + ); + --c--components--button--danger--background--color-hover: #ff2725; + --c--components--button--danger--background--color-focus-visible: var( + --c--theme--colors--danger-600 + ); + --c--components--button--danger--background--color-disabled: var( + --c--theme--colors--greyscale-100 + ); + --c--components--button--danger--color-disabled: var( + --c--theme--colors--greyscale-400 + ); + --c--components--datagrid--header--color: var( + --c--theme--colors--greyscale-600 + ); + --c--components--datagrid--header--size: 12px; + --c--components--datagrid--header--weight: 500; + --c--components--datagrid--body--background-color-hover: var( + --c--theme--colors--greyscale-100 + ); + --c--components--forms-checkbox--border-radius: 4px; + --c--components--forms-checkbox--border-color: var( + --c--theme--colors--primary-800 + ); + --c--components--forms-checkbox--background-color--hover: var( + --c--theme--colors--greyscale-100 + ); + --c--components--forms-checkbox--border--color-disabled: var( + --c--theme--colors--greyscale-200 + ); + --c--components--forms-checkbox--border--color: var( + --c--theme--colors--primary-800 + ); + --c--components--forms-checkbox--background--disabled: var( + --c--theme--colors--greyscale-200 + ); + --c--components--forms-checkbox--background--enable: var( + --c--theme--colors--primary-800 + ); + --c--components--forms-checkbox--check--disabled: var( + --c--theme--colors--greyscale-300 + ); + --c--components--forms-checkbox--check--enable: var( + --c--theme--colors--greyscale-000 + ); + --c--components--forms-checkbox--color: var(--c--theme--colors--primary-text); + --c--components--forms-checkbox--label--color: var( + --c--theme--colors--greyscale-1000 + ); + --c--components--forms-checkbox--label--size: var( + --c--theme--font--sizes--sm + ); + --c--components--forms-checkbox--label--weight: 500; + --c--components--forms-checkbox--text--color: var( + --c--theme--colors--greyscale-600 + ); + --c--components--forms-checkbox--text--size: var(--c--theme--font--sizes--s); + --c--components--forms-checkbox--text--weight: 400; + --c--components--forms-checkbox--text--color-disabled: var( + --c--theme--colors--greyscale-300 + ); + --c--components--forms-labelledbox--label-color--small: var( + --c--theme--colors--greyscale-950 + ); + --c--components--forms-labelledbox--label-color--small--disabled: var( + --c--theme--colors--greyscale-300 + ); + --c--components--forms-labelledbox--label-color--big: var( + --c--theme--colors--greyscale-950 + ); + --c--components--forms-labelledbox--label-color--big--disabled: var( + --c--theme--colors--greyscale-300 + ); + --c--components--forms-radio--border-color: var( + --c--theme--colors--primary-800 + ); + --c--components--forms-radio--background-color: var( + --c--theme--colors--greyscale-000 + ); + --c--components--forms-radio--accent-color: var( + --c--theme--colors--primary-800 + ); + --c--components--forms-radio--accent-color-disabled: var( + --c--theme--colors--greyscale-300 + ); + --c--components--forms-switch--border--color-disabled: var( + --c--theme--colors--greyscale-300 + ); + --c--components--forms-switch--border--color: var( + --c--theme--colors--primary-800 + ); + --c--components--forms-switch--handle-background-color: white; + --c--components--forms-switch--handle-background-color--disabled: var( + --c--theme--colors--greyscale-000 + ); + --c--components--forms-switch--rail-background-color--disabled: var( + --c--theme--colors--greyscale-000 + ); + --c--components--forms-switch--accent-color: var( + --c--theme--colors--primary-800 + ); + --c--components--forms-textarea--label-color--focus: var( + --c--theme--colors--greyscale-1000 + ); + --c--components--forms-textarea--border-radius: 4px; + --c--components--forms-textarea--border-color: var( + --c--theme--colors--greyscale-400 + ); + --c--components--forms-textarea--box-shadow--color--hover: var( + --c--theme--colors--greyscale-400 + ); + --c--components--forms-textarea--box-shadow--color--focus: var( + --c--theme--colors--primary-800 + ); + --c--components--forms-textarea--value-color: var( + --c--theme--colors--greyscale-950 + ); + --c--components--forms-textarea--value-color--disabled: var( + --c--theme--colors--greyscale-300 + ); + --c--components--forms-textarea--font-size: 14px; + --c--components--forms-input--label-color--focus: var( + --c--theme--colors--greyscale-1000 + ); + --c--components--forms-input--border-radius: 4px; + --c--components--forms-input--border-color: var( + --c--theme--colors--greyscale-400 + ); + --c--components--forms-input--box-shadow--color--hover: var( + --c--theme--colors--greyscale-400 + ); + --c--components--forms-input--box-shadow--color--focus: var( + --c--theme--colors--primary-800 + ); + --c--components--forms-input--value-color: var( + --c--theme--colors--greyscale-950 + ); + --c--components--forms-input--value-color--disabled: var( + --c--theme--colors--greyscale-300 + ); + --c--components--forms-input--font-size: 14px; + --c--components--forms-select--label-color--focus: var( + --c--theme--colors--greyscale-1000 + ); + --c--components--forms-select--item-font-size: 14px; + --c--components--forms-select--border-radius: 4px; + --c--components--forms-select--border-radius-hover: 4px; + --c--components--forms-select--border-color: var( + --c--theme--colors--greyscale-400 + ); + --c--components--forms-select--box-shadow--color--hover: var( + --c--theme--colors--greyscale-400 + ); + --c--components--forms-select--box-shadow--color--focus: var( + --c--theme--colors--primary-800 + ); + --c--components--forms-select--value-color: var( + --c--theme--colors--greyscale-950 + ); + --c--components--forms-select--font-size: 14px; + --c--components--badge--font-size: var(--c--theme--font--sizes--xs); + --c--components--badge--border-radius: 4px; + --c--components--badge--padding-inline: var(--c--theme--spacings--xs); + --c--components--badge--padding-block: var(--c--theme--spacings--2xs); + --c--components--badge--accent--background-color: var( + --c--theme--colors--primary-100 + ); + --c--components--badge--accent--color: var(--c--theme--colors--primary-600); + --c--components--badge--neutral--background-color: var( + --c--theme--colors--greyscale-100 + ); + --c--components--badge--neutral--color: var( + --c--theme--colors--greyscale-600 + ); + --c--components--badge--danger--background-color: var( + --c--theme--colors--danger-100 + ); + --c--components--badge--danger--color: var(--c--theme--colors--danger-600); + --c--components--badge--success--background-color: var( + --c--theme--colors--success-100 + ); + --c--components--badge--success--color: var(--c--theme--colors--success-600); + --c--components--badge--warning--background-color: var( + --c--theme--colors--warning-100 + ); + --c--components--badge--warning--color: var(--c--theme--colors--warning-600); + --c--components--badge--info--background-color: var( + --c--theme--colors--info-100 + ); + --c--components--badge--info--color: var(--c--theme--colors--info-600); + --c--components--la-gaufre: false; + --c--components--home-proconnect: false; + --c--components--beta: false; + --c--components--image-system-filter: ; + --c--components--favicon--ico: /assets/favicon-light.ico; + --c--components--favicon--png-light: /assets/favicon-light.png; + --c--components--favicon--png-dark: /assets/favicon-dark.png; +} + +.cunningham-theme--dark { + --c--theme--colors--greyscale-100: #182536; + --c--theme--colors--greyscale-200: #303c4b; + --c--theme--colors--greyscale-300: #555f6b; + --c--theme--colors--greyscale-400: #79818a; + --c--theme--colors--greyscale-500: #9ea3aa; + --c--theme--colors--greyscale-600: #c2c6ca; + --c--theme--colors--greyscale-700: #e7e8ea; + --c--theme--colors--greyscale-800: #f3f4f4; + --c--theme--colors--greyscale-900: #fafafb; + --c--theme--colors--greyscale-000: #0c1a2b; + --c--theme--colors--primary-100: #3b4c62; + --c--theme--colors--primary-200: #4d6481; + --c--theme--colors--primary-300: #6381a6; + --c--theme--colors--primary-400: #7fa5d5; + --c--theme--colors--primary-500: #8cb5ea; + --c--theme--colors--primary-600: #a3c4ee; + --c--theme--colors--primary-700: #c3d8f4; + --c--theme--colors--primary-800: #dde9f8; + --c--theme--colors--primary-900: #f4f8fd; + --c--theme--colors--success-100: #eef8d7; + --c--theme--colors--success-200: #d9f1b2; + --c--theme--colors--success-300: #bde985; + --c--theme--colors--success-400: #a0e25d; + --c--theme--colors--success-500: #76d628; + --c--theme--colors--success-600: #5bb520; + --c--theme--colors--success-700: #43941a; + --c--theme--colors--success-800: #307414; + --c--theme--colors--success-900: #225d10; + --c--theme--colors--warning-100: #f7f3d5; + --c--theme--colors--warning-200: #f0e5aa; + --c--theme--colors--warning-300: #e8d680; + --c--theme--colors--warning-400: #e3c95f; + --c--theme--colors--warning-500: #d9b32b; + --c--theme--colors--warning-600: #bd9721; + --c--theme--colors--warning-700: #9d7b1c; + --c--theme--colors--warning-800: #7e6016; + --c--theme--colors--warning-900: #684d12; + --c--theme--colors--danger-100: #f8d0d0; + --c--theme--colors--danger-200: #f09898; + --c--theme--colors--danger-300: #f09898; + --c--theme--colors--danger-400: #ed8585; + --c--theme--colors--danger-500: #e96666; + --c--theme--colors--danger-600: #d66; + --c--theme--colors--danger-700: #c36666; + --c--theme--colors--danger-800: #ae6666; + --c--theme--colors--danger-900: #9d6666; +} + +.cunningham-theme--dsfr { + --c--theme--colors--secondary-icon: #c9191e; + --c--theme--logo--src: /assets/logo-gouv.svg; + --c--theme--logo--widthHeader: 110px; + --c--theme--logo--widthFooter: 220px; + --c--theme--logo--alt: gouvernement logo; + --c--components--la-gaufre: true; + --c--components--home-proconnect: true; + --c--components--beta: true; + --c--components--favicon--ico: /assets/favicon-dsfr.ico; + --c--components--favicon--png-light: /assets/favicon-dsfr.png; + --c--components--favicon--png-dark: /assets/favicon-dark-dsfr.png; +} + +.cunningham-theme--generic { + --c--theme--colors--primary-action: #206ebd; + --c--theme--colors--primary-focus: #1e64bf; + --c--theme--colors--primary-text: #2e2c28; + --c--theme--colors--primary-050: #f8f8f7; + --c--theme--colors--primary-100: #f0efec; + --c--theme--colors--primary-150: #f4f4fd; + --c--theme--colors--primary-200: #e8e7e4; + --c--theme--colors--primary-300: #cfcdc9; + --c--theme--colors--primary-400: #979592; + --c--theme--colors--primary-500: #82807d; + --c--theme--colors--primary-600: #3f3d39; + --c--theme--colors--primary-700: #2e2c28; + --c--theme--colors--primary-800: #302e29; + --c--theme--colors--primary-900: #282622; + --c--theme--colors--primary-950: #201f1c; + --c--theme--colors--secondary-text: #fff; + --c--theme--colors--secondary-50: #f4f7fa; + --c--theme--colors--secondary-100: #d7e3ee; + --c--theme--colors--secondary-200: #b8cce1; + --c--theme--colors--secondary-300: #99b4d3; + --c--theme--colors--secondary-400: #7595be; + --c--theme--colors--secondary-500: #5874a0; + --c--theme--colors--secondary-600: #3a5383; + --c--theme--colors--secondary-700: #1e3462; + --c--theme--colors--secondary-800: #091b41; + --c--theme--colors--secondary-900: #08183b; + --c--theme--colors--secondary-950: #071636; + --c--theme--colors--greyscale-text: #3c3b38; + --c--theme--colors--greyscale-000: #fff; + --c--theme--colors--greyscale-050: #f8f7f7; + --c--theme--colors--greyscale-100: #f3f3f2; + --c--theme--colors--greyscale-200: #ecebea; + --c--theme--colors--greyscale-250: #e4e3e2; + --c--theme--colors--greyscale-300: #d3d2cf; + --c--theme--colors--greyscale-350: #eee; + --c--theme--colors--greyscale-400: #96948e; + --c--theme--colors--greyscale-500: #817e77; + --c--theme--colors--greyscale-600: #6a6862; + --c--theme--colors--greyscale-700: #3c3b38; + --c--theme--colors--greyscale-750: #383632; + --c--theme--colors--greyscale-800: #2d2b27; + --c--theme--colors--greyscale-900: #262522; + --c--theme--colors--greyscale-950: #201f1c; + --c--theme--colors--greyscale-1000: #181714; + --c--theme--colors--success-text: #234935; + --c--theme--colors--success-50: #f3fbf5; + --c--theme--colors--success-100: #e4f7ea; + --c--theme--colors--success-200: #caeed4; + --c--theme--colors--success-300: #a0e0b5; + --c--theme--colors--success-400: #6cc88c; + --c--theme--colors--success-500: #6cc88c; + --c--theme--colors--success-600: #358d5c; + --c--theme--colors--success-700: #2d704b; + --c--theme--colors--success-800: #28583f; + --c--theme--colors--success-900: #234935; + --c--theme--colors--success-950: #0f281b; + --c--theme--colors--info-text: #212445; + --c--theme--colors--info-50: #f2f6fb; + --c--theme--colors--info-100: #e2e9f5; + --c--theme--colors--info-200: #ccd8ee; + --c--theme--colors--info-300: #a9c0e3; + --c--theme--colors--info-400: #809dd4; + --c--theme--colors--info-500: #617bc7; + --c--theme--colors--info-600: #4a5cbf; + --c--theme--colors--info-700: #3e49b2; + --c--theme--colors--info-800: #353c8f; + --c--theme--colors--info-900: #303771; + --c--theme--colors--info-950: #212445; + --c--theme--colors--warning-text: #d97c3a; + --c--theme--colors--warning-50: #fdf7f1; + --c--theme--colors--warning-100: #fbeddc; + --c--theme--colors--warning-200: #f5d9b9; + --c--theme--colors--warning-300: #edbe8c; + --c--theme--colors--warning-400: #e2985c; + --c--theme--colors--warning-500: #d97c3a; + --c--theme--colors--warning-600: #c96330; + --c--theme--colors--warning-700: #a34b32; + --c--theme--colors--warning-800: #813b2c; + --c--theme--colors--warning-900: #693327; + --c--theme--colors--warning-950: #381713; + --c--theme--colors--danger-action: #c0182a; + --c--theme--colors--danger-text: #fff; + --c--theme--colors--danger-050: #fdf5f4; + --c--theme--colors--danger-100: #fbebe8; + --c--theme--colors--danger-200: #f9e0dc; + --c--theme--colors--danger-300: #f3c3bd; + --c--theme--colors--danger-400: #e26552; + --c--theme--colors--danger-500: #c91f00; + --c--theme--colors--danger-600: #a71901; + --c--theme--colors--danger-700: #562c2b; + --c--theme--colors--danger-800: #392425; + --c--theme--colors--danger-900: #311f20; + --c--theme--colors--danger-950: #2a191a; + --c--theme--colors--blue-400: #8baecc; + --c--theme--colors--blue-500: #567aa2; + --c--theme--colors--blue-600: #455784; + --c--theme--colors--brown-400: #e4c090; + --c--theme--colors--brown-500: #ba9977; + --c--theme--colors--brown-600: #735c45; + --c--theme--colors--cyan-400: #5cbec9; + --c--theme--colors--cyan-500: #43a1b3; + --c--theme--colors--cyan-600: #39809b; + --c--theme--colors--gold-400: #ecbf50; + --c--theme--colors--gold-500: #dfa038; + --c--theme--colors--gold-600: #c17b31; + --c--theme--colors--green-400: #5dbd9a; + --c--theme--colors--green-500: #3aa183; + --c--theme--colors--green-600: #2a816d; + --c--theme--colors--olive-400: #afd662; + --c--theme--colors--olive-500: #90bb4b; + --c--theme--colors--olive-600: #6e9441; + --c--theme--colors--orange-400: #e2985c; + --c--theme--colors--orange-500: #d97c3a; + --c--theme--colors--orange-600: #c96330; + --c--theme--colors--pink-400: #be8fc8; + --c--theme--colors--pink-500: #a563b1; + --c--theme--colors--pink-600: #8b44a5; + --c--theme--colors--purple-400: #be8fc8; + --c--theme--colors--purple-500: #a563b1; + --c--theme--colors--purple-600: #8b44a5; + --c--theme--colors--yellow-400: #edc947; + --c--theme--colors--yellow-500: #dbb13a; + --c--theme--colors--yellow-600: #b88a34; + --c--theme--font--families--base: inter, roboto flex variable, sans-serif; + --c--theme--font--families--accent: inter, roboto flex variable, sans-serif; + --c--components--button--primary--background--color-hover: var( + --c--theme--colors--primary-focus + ); + --c--components--button--primary--background--color-active: var( + --c--theme--colors--primary-focus + ); + --c--components--button--primary--background--color-focus: var( + --c--theme--colors--primary-focus + ); + --c--components--image-system-filter: saturate(0.2); +} + +.clr-secondary-text { + color: var(--c--theme--colors--secondary-text); +} + +.clr-secondary-100 { + color: var(--c--theme--colors--secondary-100); +} + +.clr-secondary-200 { + color: var(--c--theme--colors--secondary-200); +} + +.clr-secondary-300 { + color: var(--c--theme--colors--secondary-300); +} + +.clr-secondary-400 { + color: var(--c--theme--colors--secondary-400); +} + +.clr-secondary-500 { + color: var(--c--theme--colors--secondary-500); +} + +.clr-secondary-600 { + color: var(--c--theme--colors--secondary-600); +} + +.clr-secondary-700 { + color: var(--c--theme--colors--secondary-700); +} + +.clr-secondary-800 { + color: var(--c--theme--colors--secondary-800); +} + +.clr-secondary-900 { + color: var(--c--theme--colors--secondary-900); +} + +.clr-info-text { + color: var(--c--theme--colors--info-text); +} + +.clr-info-100 { + color: var(--c--theme--colors--info-100); +} + +.clr-info-200 { + color: var(--c--theme--colors--info-200); +} + +.clr-info-300 { + color: var(--c--theme--colors--info-300); +} + +.clr-info-400 { + color: var(--c--theme--colors--info-400); +} + +.clr-info-500 { + color: var(--c--theme--colors--info-500); +} + +.clr-info-600 { + color: var(--c--theme--colors--info-600); +} + +.clr-info-700 { + color: var(--c--theme--colors--info-700); +} + +.clr-info-800 { + color: var(--c--theme--colors--info-800); +} + +.clr-info-900 { + color: var(--c--theme--colors--info-900); +} + +.clr-greyscale-100 { + color: var(--c--theme--colors--greyscale-100); +} + +.clr-greyscale-200 { + color: var(--c--theme--colors--greyscale-200); +} + +.clr-greyscale-300 { + color: var(--c--theme--colors--greyscale-300); +} + +.clr-greyscale-400 { + color: var(--c--theme--colors--greyscale-400); +} + +.clr-greyscale-500 { + color: var(--c--theme--colors--greyscale-500); +} + +.clr-greyscale-600 { + color: var(--c--theme--colors--greyscale-600); +} + +.clr-greyscale-700 { + color: var(--c--theme--colors--greyscale-700); +} + +.clr-greyscale-800 { + color: var(--c--theme--colors--greyscale-800); +} + +.clr-greyscale-900 { + color: var(--c--theme--colors--greyscale-900); +} + +.clr-greyscale-000 { + color: var(--c--theme--colors--greyscale-000); +} + +.clr-primary-100 { + color: var(--c--theme--colors--primary-100); +} + +.clr-primary-200 { + color: var(--c--theme--colors--primary-200); +} + +.clr-primary-300 { + color: var(--c--theme--colors--primary-300); +} + +.clr-primary-400 { + color: var(--c--theme--colors--primary-400); +} + +.clr-primary-500 { + color: var(--c--theme--colors--primary-500); +} + +.clr-primary-600 { + color: var(--c--theme--colors--primary-600); +} + +.clr-primary-700 { + color: var(--c--theme--colors--primary-700); +} + +.clr-primary-800 { + color: var(--c--theme--colors--primary-800); +} + +.clr-primary-900 { + color: var(--c--theme--colors--primary-900); +} + +.clr-success-100 { + color: var(--c--theme--colors--success-100); +} + +.clr-success-200 { + color: var(--c--theme--colors--success-200); +} + +.clr-success-300 { + color: var(--c--theme--colors--success-300); +} + +.clr-success-400 { + color: var(--c--theme--colors--success-400); +} + +.clr-success-500 { + color: var(--c--theme--colors--success-500); +} + +.clr-success-600 { + color: var(--c--theme--colors--success-600); +} + +.clr-success-700 { + color: var(--c--theme--colors--success-700); +} + +.clr-success-800 { + color: var(--c--theme--colors--success-800); +} + +.clr-success-900 { + color: var(--c--theme--colors--success-900); +} + +.clr-warning-100 { + color: var(--c--theme--colors--warning-100); +} + +.clr-warning-200 { + color: var(--c--theme--colors--warning-200); +} + +.clr-warning-300 { + color: var(--c--theme--colors--warning-300); +} + +.clr-warning-400 { + color: var(--c--theme--colors--warning-400); +} + +.clr-warning-500 { + color: var(--c--theme--colors--warning-500); +} + +.clr-warning-600 { + color: var(--c--theme--colors--warning-600); +} + +.clr-warning-700 { + color: var(--c--theme--colors--warning-700); +} + +.clr-warning-800 { + color: var(--c--theme--colors--warning-800); +} + +.clr-warning-900 { + color: var(--c--theme--colors--warning-900); +} + +.clr-danger-100 { + color: var(--c--theme--colors--danger-100); +} + +.clr-danger-200 { + color: var(--c--theme--colors--danger-200); +} + +.clr-danger-300 { + color: var(--c--theme--colors--danger-300); +} + +.clr-danger-400 { + color: var(--c--theme--colors--danger-400); +} + +.clr-danger-500 { + color: var(--c--theme--colors--danger-500); +} + +.clr-danger-600 { + color: var(--c--theme--colors--danger-600); +} + +.clr-danger-700 { + color: var(--c--theme--colors--danger-700); +} + +.clr-danger-800 { + color: var(--c--theme--colors--danger-800); +} + +.clr-danger-900 { + color: var(--c--theme--colors--danger-900); +} + +.clr-primary-text { + color: var(--c--theme--colors--primary-text); +} + +.clr-success-text { + color: var(--c--theme--colors--success-text); +} + +.clr-warning-text { + color: var(--c--theme--colors--warning-text); +} + +.clr-danger-text { + color: var(--c--theme--colors--danger-text); +} + +.clr-primary-050 { + color: var(--c--theme--colors--primary-050); +} + +.clr-primary-150 { + color: var(--c--theme--colors--primary-150); +} + +.clr-greyscale-text { + color: var(--c--theme--colors--greyscale-text); +} + +.clr-greyscale-050 { + color: var(--c--theme--colors--greyscale-050); +} + +.clr-greyscale-250 { + color: var(--c--theme--colors--greyscale-250); +} + +.clr-greyscale-350 { + color: var(--c--theme--colors--greyscale-350); +} + +.clr-greyscale-750 { + color: var(--c--theme--colors--greyscale-750); +} + +.clr-greyscale-950 { + color: var(--c--theme--colors--greyscale-950); +} + +.clr-greyscale-1000 { + color: var(--c--theme--colors--greyscale-1000); +} + +.clr-danger-050 { + color: var(--c--theme--colors--danger-050); +} + +.clr-blue-500 { + color: var(--c--theme--colors--blue-500); +} + +.clr-brown-500 { + color: var(--c--theme--colors--brown-500); +} + +.clr-cyan-500 { + color: var(--c--theme--colors--cyan-500); +} + +.clr-gold-500 { + color: var(--c--theme--colors--gold-500); +} + +.clr-green-500 { + color: var(--c--theme--colors--green-500); +} + +.clr-olive-500 { + color: var(--c--theme--colors--olive-500); +} + +.clr-orange-500 { + color: var(--c--theme--colors--orange-500); +} + +.clr-purple-500 { + color: var(--c--theme--colors--purple-500); +} + +.clr-red-500 { + color: var(--c--theme--colors--red-500); +} + +.clr-yellow-500 { + color: var(--c--theme--colors--yellow-500); +} + +.clr-rose-500 { + color: var(--c--theme--colors--rose-500); +} + +.clr-primary-action { + color: var(--c--theme--colors--primary-action); +} + +.clr-primary-bg { + color: var(--c--theme--colors--primary-bg); +} + +.clr-primary-focus { + color: var(--c--theme--colors--primary-focus); +} + +.clr-secondary-icon { + color: var(--c--theme--colors--secondary-icon); +} + +.clr-blue-400 { + color: var(--c--theme--colors--blue-400); +} + +.clr-blue-600 { + color: var(--c--theme--colors--blue-600); +} + +.clr-brown-400 { + color: var(--c--theme--colors--brown-400); +} + +.clr-brown-600 { + color: var(--c--theme--colors--brown-600); +} + +.clr-cyan-400 { + color: var(--c--theme--colors--cyan-400); +} + +.clr-cyan-600 { + color: var(--c--theme--colors--cyan-600); +} + +.clr-gold-400 { + color: var(--c--theme--colors--gold-400); +} + +.clr-gold-600 { + color: var(--c--theme--colors--gold-600); +} + +.clr-green-400 { + color: var(--c--theme--colors--green-400); +} + +.clr-green-600 { + color: var(--c--theme--colors--green-600); +} + +.clr-olive-400 { + color: var(--c--theme--colors--olive-400); +} + +.clr-olive-600 { + color: var(--c--theme--colors--olive-600); +} + +.clr-orange-400 { + color: var(--c--theme--colors--orange-400); +} + +.clr-orange-600 { + color: var(--c--theme--colors--orange-600); +} + +.clr-pink-400 { + color: var(--c--theme--colors--pink-400); +} + +.clr-pink-500 { + color: var(--c--theme--colors--pink-500); +} + +.clr-pink-600 { + color: var(--c--theme--colors--pink-600); +} + +.clr-purple-400 { + color: var(--c--theme--colors--purple-400); +} + +.clr-purple-600 { + color: var(--c--theme--colors--purple-600); +} + +.clr-yellow-400 { + color: var(--c--theme--colors--yellow-400); +} + +.clr-yellow-600 { + color: var(--c--theme--colors--yellow-600); +} + +.bg-secondary-text { + background-color: var(--c--theme--colors--secondary-text); +} + +.bg-secondary-100 { + background-color: var(--c--theme--colors--secondary-100); +} + +.bg-secondary-200 { + background-color: var(--c--theme--colors--secondary-200); +} + +.bg-secondary-300 { + background-color: var(--c--theme--colors--secondary-300); +} + +.bg-secondary-400 { + background-color: var(--c--theme--colors--secondary-400); +} + +.bg-secondary-500 { + background-color: var(--c--theme--colors--secondary-500); +} + +.bg-secondary-600 { + background-color: var(--c--theme--colors--secondary-600); +} + +.bg-secondary-700 { + background-color: var(--c--theme--colors--secondary-700); +} + +.bg-secondary-800 { + background-color: var(--c--theme--colors--secondary-800); +} + +.bg-secondary-900 { + background-color: var(--c--theme--colors--secondary-900); +} + +.bg-info-text { + background-color: var(--c--theme--colors--info-text); +} + +.bg-info-100 { + background-color: var(--c--theme--colors--info-100); +} + +.bg-info-200 { + background-color: var(--c--theme--colors--info-200); +} + +.bg-info-300 { + background-color: var(--c--theme--colors--info-300); +} + +.bg-info-400 { + background-color: var(--c--theme--colors--info-400); +} + +.bg-info-500 { + background-color: var(--c--theme--colors--info-500); +} + +.bg-info-600 { + background-color: var(--c--theme--colors--info-600); +} + +.bg-info-700 { + background-color: var(--c--theme--colors--info-700); +} + +.bg-info-800 { + background-color: var(--c--theme--colors--info-800); +} + +.bg-info-900 { + background-color: var(--c--theme--colors--info-900); +} + +.bg-greyscale-100 { + background-color: var(--c--theme--colors--greyscale-100); +} + +.bg-greyscale-200 { + background-color: var(--c--theme--colors--greyscale-200); +} + +.bg-greyscale-300 { + background-color: var(--c--theme--colors--greyscale-300); +} + +.bg-greyscale-400 { + background-color: var(--c--theme--colors--greyscale-400); +} + +.bg-greyscale-500 { + background-color: var(--c--theme--colors--greyscale-500); +} + +.bg-greyscale-600 { + background-color: var(--c--theme--colors--greyscale-600); +} + +.bg-greyscale-700 { + background-color: var(--c--theme--colors--greyscale-700); +} + +.bg-greyscale-800 { + background-color: var(--c--theme--colors--greyscale-800); +} + +.bg-greyscale-900 { + background-color: var(--c--theme--colors--greyscale-900); +} + +.bg-greyscale-000 { + background-color: var(--c--theme--colors--greyscale-000); +} + +.bg-primary-100 { + background-color: var(--c--theme--colors--primary-100); +} + +.bg-primary-200 { + background-color: var(--c--theme--colors--primary-200); +} + +.bg-primary-300 { + background-color: var(--c--theme--colors--primary-300); +} + +.bg-primary-400 { + background-color: var(--c--theme--colors--primary-400); +} + +.bg-primary-500 { + background-color: var(--c--theme--colors--primary-500); +} + +.bg-primary-600 { + background-color: var(--c--theme--colors--primary-600); +} + +.bg-primary-700 { + background-color: var(--c--theme--colors--primary-700); +} + +.bg-primary-800 { + background-color: var(--c--theme--colors--primary-800); +} + +.bg-primary-900 { + background-color: var(--c--theme--colors--primary-900); +} + +.bg-success-100 { + background-color: var(--c--theme--colors--success-100); +} + +.bg-success-200 { + background-color: var(--c--theme--colors--success-200); +} + +.bg-success-300 { + background-color: var(--c--theme--colors--success-300); +} + +.bg-success-400 { + background-color: var(--c--theme--colors--success-400); +} + +.bg-success-500 { + background-color: var(--c--theme--colors--success-500); +} + +.bg-success-600 { + background-color: var(--c--theme--colors--success-600); +} + +.bg-success-700 { + background-color: var(--c--theme--colors--success-700); +} + +.bg-success-800 { + background-color: var(--c--theme--colors--success-800); +} + +.bg-success-900 { + background-color: var(--c--theme--colors--success-900); +} + +.bg-warning-100 { + background-color: var(--c--theme--colors--warning-100); +} + +.bg-warning-200 { + background-color: var(--c--theme--colors--warning-200); +} + +.bg-warning-300 { + background-color: var(--c--theme--colors--warning-300); +} + +.bg-warning-400 { + background-color: var(--c--theme--colors--warning-400); +} + +.bg-warning-500 { + background-color: var(--c--theme--colors--warning-500); +} + +.bg-warning-600 { + background-color: var(--c--theme--colors--warning-600); +} + +.bg-warning-700 { + background-color: var(--c--theme--colors--warning-700); +} + +.bg-warning-800 { + background-color: var(--c--theme--colors--warning-800); +} + +.bg-warning-900 { + background-color: var(--c--theme--colors--warning-900); +} + +.bg-danger-100 { + background-color: var(--c--theme--colors--danger-100); +} + +.bg-danger-200 { + background-color: var(--c--theme--colors--danger-200); +} + +.bg-danger-300 { + background-color: var(--c--theme--colors--danger-300); +} + +.bg-danger-400 { + background-color: var(--c--theme--colors--danger-400); +} + +.bg-danger-500 { + background-color: var(--c--theme--colors--danger-500); +} + +.bg-danger-600 { + background-color: var(--c--theme--colors--danger-600); +} + +.bg-danger-700 { + background-color: var(--c--theme--colors--danger-700); +} + +.bg-danger-800 { + background-color: var(--c--theme--colors--danger-800); +} + +.bg-danger-900 { + background-color: var(--c--theme--colors--danger-900); +} + +.bg-primary-text { + background-color: var(--c--theme--colors--primary-text); +} + +.bg-success-text { + background-color: var(--c--theme--colors--success-text); +} + +.bg-warning-text { + background-color: var(--c--theme--colors--warning-text); +} + +.bg-danger-text { + background-color: var(--c--theme--colors--danger-text); +} + +.bg-primary-050 { + background-color: var(--c--theme--colors--primary-050); +} + +.bg-primary-150 { + background-color: var(--c--theme--colors--primary-150); +} + +.bg-greyscale-text { + background-color: var(--c--theme--colors--greyscale-text); +} + +.bg-greyscale-050 { + background-color: var(--c--theme--colors--greyscale-050); +} + +.bg-greyscale-250 { + background-color: var(--c--theme--colors--greyscale-250); +} + +.bg-greyscale-350 { + background-color: var(--c--theme--colors--greyscale-350); +} + +.bg-greyscale-750 { + background-color: var(--c--theme--colors--greyscale-750); +} + +.bg-greyscale-950 { + background-color: var(--c--theme--colors--greyscale-950); +} + +.bg-greyscale-1000 { + background-color: var(--c--theme--colors--greyscale-1000); +} + +.bg-danger-050 { + background-color: var(--c--theme--colors--danger-050); +} + +.bg-blue-500 { + background-color: var(--c--theme--colors--blue-500); +} + +.bg-brown-500 { + background-color: var(--c--theme--colors--brown-500); +} + +.bg-cyan-500 { + background-color: var(--c--theme--colors--cyan-500); +} + +.bg-gold-500 { + background-color: var(--c--theme--colors--gold-500); +} + +.bg-green-500 { + background-color: var(--c--theme--colors--green-500); +} + +.bg-olive-500 { + background-color: var(--c--theme--colors--olive-500); +} + +.bg-orange-500 { + background-color: var(--c--theme--colors--orange-500); +} + +.bg-purple-500 { + background-color: var(--c--theme--colors--purple-500); +} + +.bg-red-500 { + background-color: var(--c--theme--colors--red-500); +} + +.bg-yellow-500 { + background-color: var(--c--theme--colors--yellow-500); +} + +.bg-rose-500 { + background-color: var(--c--theme--colors--rose-500); +} + +.bg-primary-action { + background-color: var(--c--theme--colors--primary-action); +} + +.bg-primary-bg { + background-color: var(--c--theme--colors--primary-bg); +} + +.bg-primary-focus { + background-color: var(--c--theme--colors--primary-focus); +} + +.bg-secondary-icon { + background-color: var(--c--theme--colors--secondary-icon); +} + +.bg-blue-400 { + background-color: var(--c--theme--colors--blue-400); +} + +.bg-blue-600 { + background-color: var(--c--theme--colors--blue-600); +} + +.bg-brown-400 { + background-color: var(--c--theme--colors--brown-400); +} + +.bg-brown-600 { + background-color: var(--c--theme--colors--brown-600); +} + +.bg-cyan-400 { + background-color: var(--c--theme--colors--cyan-400); +} + +.bg-cyan-600 { + background-color: var(--c--theme--colors--cyan-600); +} + +.bg-gold-400 { + background-color: var(--c--theme--colors--gold-400); +} + +.bg-gold-600 { + background-color: var(--c--theme--colors--gold-600); +} + +.bg-green-400 { + background-color: var(--c--theme--colors--green-400); +} + +.bg-green-600 { + background-color: var(--c--theme--colors--green-600); +} + +.bg-olive-400 { + background-color: var(--c--theme--colors--olive-400); +} + +.bg-olive-600 { + background-color: var(--c--theme--colors--olive-600); +} + +.bg-orange-400 { + background-color: var(--c--theme--colors--orange-400); +} + +.bg-orange-600 { + background-color: var(--c--theme--colors--orange-600); +} + +.bg-pink-400 { + background-color: var(--c--theme--colors--pink-400); +} + +.bg-pink-500 { + background-color: var(--c--theme--colors--pink-500); +} + +.bg-pink-600 { + background-color: var(--c--theme--colors--pink-600); +} + +.bg-purple-400 { + background-color: var(--c--theme--colors--purple-400); +} + +.bg-purple-600 { + background-color: var(--c--theme--colors--purple-600); +} + +.bg-yellow-400 { + background-color: var(--c--theme--colors--yellow-400); +} + +.bg-yellow-600 { + background-color: var(--c--theme--colors--yellow-600); +} + +.fw-thin { + font-weight: var(--c--theme--font--weights--thin); +} + +.fw-light { + font-weight: var(--c--theme--font--weights--light); +} + +.fw-regular { + font-weight: var(--c--theme--font--weights--regular); +} + +.fw-medium { + font-weight: var(--c--theme--font--weights--medium); +} + +.fw-bold { + font-weight: var(--c--theme--font--weights--bold); +} + +.fw-extrabold { + font-weight: var(--c--theme--font--weights--extrabold); +} + +.fw-black { + font-weight: var(--c--theme--font--weights--black); +} + +.fs-h1 { + font-size: var(--c--theme--font--sizes--h1); + letter-spacing: var(--c--theme--font--letterspacings--h1); +} + +.fs-h2 { + font-size: var(--c--theme--font--sizes--h2); + letter-spacing: var(--c--theme--font--letterspacings--h2); +} + +.fs-h3 { + font-size: var(--c--theme--font--sizes--h3); + letter-spacing: var(--c--theme--font--letterspacings--h3); +} + +.fs-h4 { + font-size: var(--c--theme--font--sizes--h4); + letter-spacing: var(--c--theme--font--letterspacings--h4); +} + +.fs-h5 { + font-size: var(--c--theme--font--sizes--h5); + letter-spacing: var(--c--theme--font--letterspacings--h5); +} + +.fs-h6 { + font-size: var(--c--theme--font--sizes--h6); + letter-spacing: var(--c--theme--font--letterspacings--h6); +} + +.fs-l { + font-size: var(--c--theme--font--sizes--l); + letter-spacing: var(--c--theme--font--letterspacings--l); +} + +.fs-m { + font-size: var(--c--theme--font--sizes--m); + letter-spacing: var(--c--theme--font--letterspacings--m); +} + +.fs-s { + font-size: var(--c--theme--font--sizes--s); + letter-spacing: var(--c--theme--font--letterspacings--s); +} + +.fs-xs { + font-size: var(--c--theme--font--sizes--xs); + letter-spacing: var(--c--theme--font--letterspacings--xs); +} + +.fs-sm { + font-size: var(--c--theme--font--sizes--sm); + letter-spacing: var(--c--theme--font--letterspacings--sm); +} + +.fs-md { + font-size: var(--c--theme--font--sizes--md); + letter-spacing: var(--c--theme--font--letterspacings--md); +} + +.fs-lg { + font-size: var(--c--theme--font--sizes--lg); + letter-spacing: var(--c--theme--font--letterspacings--lg); +} + +.fs-ml { + font-size: var(--c--theme--font--sizes--ml); + letter-spacing: var(--c--theme--font--letterspacings--ml); +} + +.fs-xl { + font-size: var(--c--theme--font--sizes--xl); + letter-spacing: var(--c--theme--font--letterspacings--xl); +} + +.fs-t { + font-size: var(--c--theme--font--sizes--t); + letter-spacing: var(--c--theme--font--letterspacings--t); +} + +.fs-xl-alt { + font-size: var(--c--theme--font--sizes--xl-alt); + letter-spacing: var(--c--theme--font--letterspacings--xl-alt); +} + +.fs-lg-alt { + font-size: var(--c--theme--font--sizes--lg-alt); + letter-spacing: var(--c--theme--font--letterspacings--lg-alt); +} + +.fs-md-alt { + font-size: var(--c--theme--font--sizes--md-alt); + letter-spacing: var(--c--theme--font--letterspacings--md-alt); +} + +.fs-sm-alt { + font-size: var(--c--theme--font--sizes--sm-alt); + letter-spacing: var(--c--theme--font--letterspacings--sm-alt); +} + +.fs-xs-alt { + font-size: var(--c--theme--font--sizes--xs-alt); + letter-spacing: var(--c--theme--font--letterspacings--xs-alt); +} + +.f-base { + font-family: var(--c--theme--font--families--base); +} + +.f-accent { + font-family: var(--c--theme--font--families--accent); +} + +.m-0 { + margin: var(--c--theme--spacings--0); +} + +.mb-0 { + margin-bottom: var(--c--theme--spacings--0); +} + +.mt-0 { + margin-top: var(--c--theme--spacings--0); +} + +.ml-0 { + margin-left: var(--c--theme--spacings--0); +} + +.mr-0 { + margin-right: var(--c--theme--spacings--0); +} + +.m-xl { + margin: var(--c--theme--spacings--xl); +} + +.mb-xl { + margin-bottom: var(--c--theme--spacings--xl); +} + +.mt-xl { + margin-top: var(--c--theme--spacings--xl); +} + +.ml-xl { + margin-left: var(--c--theme--spacings--xl); +} + +.mr-xl { + margin-right: var(--c--theme--spacings--xl); +} + +.m-l { + margin: var(--c--theme--spacings--l); +} + +.mb-l { + margin-bottom: var(--c--theme--spacings--l); +} + +.mt-l { + margin-top: var(--c--theme--spacings--l); +} + +.ml-l { + margin-left: var(--c--theme--spacings--l); +} + +.mr-l { + margin-right: var(--c--theme--spacings--l); +} + +.m-b { + margin: var(--c--theme--spacings--b); +} + +.mb-b { + margin-bottom: var(--c--theme--spacings--b); +} + +.mt-b { + margin-top: var(--c--theme--spacings--b); +} + +.ml-b { + margin-left: var(--c--theme--spacings--b); +} + +.mr-b { + margin-right: var(--c--theme--spacings--b); +} + +.m-s { + margin: var(--c--theme--spacings--s); +} + +.mb-s { + margin-bottom: var(--c--theme--spacings--s); +} + +.mt-s { + margin-top: var(--c--theme--spacings--s); +} + +.ml-s { + margin-left: var(--c--theme--spacings--s); +} + +.mr-s { + margin-right: var(--c--theme--spacings--s); +} + +.m-t { + margin: var(--c--theme--spacings--t); +} + +.mb-t { + margin-bottom: var(--c--theme--spacings--t); +} + +.mt-t { + margin-top: var(--c--theme--spacings--t); +} + +.ml-t { + margin-left: var(--c--theme--spacings--t); +} + +.mr-t { + margin-right: var(--c--theme--spacings--t); +} + +.m-st { + margin: var(--c--theme--spacings--st); +} + +.mb-st { + margin-bottom: var(--c--theme--spacings--st); +} + +.mt-st { + margin-top: var(--c--theme--spacings--st); +} + +.ml-st { + margin-left: var(--c--theme--spacings--st); +} + +.mr-st { + margin-right: var(--c--theme--spacings--st); +} + +.m-none { + margin: var(--c--theme--spacings--none); +} + +.mb-none { + margin-bottom: var(--c--theme--spacings--none); +} + +.mt-none { + margin-top: var(--c--theme--spacings--none); +} + +.ml-none { + margin-left: var(--c--theme--spacings--none); +} + +.mr-none { + margin-right: var(--c--theme--spacings--none); +} + +.m-auto { + margin: var(--c--theme--spacings--auto); +} + +.mb-auto { + margin-bottom: var(--c--theme--spacings--auto); +} + +.mt-auto { + margin-top: var(--c--theme--spacings--auto); +} + +.ml-auto { + margin-left: var(--c--theme--spacings--auto); +} + +.mr-auto { + margin-right: var(--c--theme--spacings--auto); +} + +.m-bx { + margin: var(--c--theme--spacings--bx); +} + +.mb-bx { + margin-bottom: var(--c--theme--spacings--bx); +} + +.mt-bx { + margin-top: var(--c--theme--spacings--bx); +} + +.ml-bx { + margin-left: var(--c--theme--spacings--bx); +} + +.mr-bx { + margin-right: var(--c--theme--spacings--bx); +} + +.m-full { + margin: var(--c--theme--spacings--full); +} + +.mb-full { + margin-bottom: var(--c--theme--spacings--full); +} + +.mt-full { + margin-top: var(--c--theme--spacings--full); +} + +.ml-full { + margin-left: var(--c--theme--spacings--full); +} + +.mr-full { + margin-right: var(--c--theme--spacings--full); +} + +.m-4xs { + margin: var(--c--theme--spacings--4xs); +} + +.mb-4xs { + margin-bottom: var(--c--theme--spacings--4xs); +} + +.mt-4xs { + margin-top: var(--c--theme--spacings--4xs); +} + +.ml-4xs { + margin-left: var(--c--theme--spacings--4xs); +} + +.mr-4xs { + margin-right: var(--c--theme--spacings--4xs); +} + +.m-3xs { + margin: var(--c--theme--spacings--3xs); +} + +.mb-3xs { + margin-bottom: var(--c--theme--spacings--3xs); +} + +.mt-3xs { + margin-top: var(--c--theme--spacings--3xs); +} + +.ml-3xs { + margin-left: var(--c--theme--spacings--3xs); +} + +.mr-3xs { + margin-right: var(--c--theme--spacings--3xs); +} + +.m-2xs { + margin: var(--c--theme--spacings--2xs); +} + +.mb-2xs { + margin-bottom: var(--c--theme--spacings--2xs); +} + +.mt-2xs { + margin-top: var(--c--theme--spacings--2xs); +} + +.ml-2xs { + margin-left: var(--c--theme--spacings--2xs); +} + +.mr-2xs { + margin-right: var(--c--theme--spacings--2xs); +} + +.m-xs { + margin: var(--c--theme--spacings--xs); +} + +.mb-xs { + margin-bottom: var(--c--theme--spacings--xs); +} + +.mt-xs { + margin-top: var(--c--theme--spacings--xs); +} + +.ml-xs { + margin-left: var(--c--theme--spacings--xs); +} + +.mr-xs { + margin-right: var(--c--theme--spacings--xs); +} + +.m-sm { + margin: var(--c--theme--spacings--sm); +} + +.mb-sm { + margin-bottom: var(--c--theme--spacings--sm); +} + +.mt-sm { + margin-top: var(--c--theme--spacings--sm); +} + +.ml-sm { + margin-left: var(--c--theme--spacings--sm); +} + +.mr-sm { + margin-right: var(--c--theme--spacings--sm); +} + +.m-base { + margin: var(--c--theme--spacings--base); +} + +.mb-base { + margin-bottom: var(--c--theme--spacings--base); +} + +.mt-base { + margin-top: var(--c--theme--spacings--base); +} + +.ml-base { + margin-left: var(--c--theme--spacings--base); +} + +.mr-base { + margin-right: var(--c--theme--spacings--base); +} + +.m-md { + margin: var(--c--theme--spacings--md); +} + +.mb-md { + margin-bottom: var(--c--theme--spacings--md); +} + +.mt-md { + margin-top: var(--c--theme--spacings--md); +} + +.ml-md { + margin-left: var(--c--theme--spacings--md); +} + +.mr-md { + margin-right: var(--c--theme--spacings--md); +} + +.m-lg { + margin: var(--c--theme--spacings--lg); +} + +.mb-lg { + margin-bottom: var(--c--theme--spacings--lg); +} + +.mt-lg { + margin-top: var(--c--theme--spacings--lg); +} + +.ml-lg { + margin-left: var(--c--theme--spacings--lg); +} + +.mr-lg { + margin-right: var(--c--theme--spacings--lg); +} + +.m-xxl { + margin: var(--c--theme--spacings--xxl); +} + +.mb-xxl { + margin-bottom: var(--c--theme--spacings--xxl); +} + +.mt-xxl { + margin-top: var(--c--theme--spacings--xxl); +} + +.ml-xxl { + margin-left: var(--c--theme--spacings--xxl); +} + +.mr-xxl { + margin-right: var(--c--theme--spacings--xxl); +} + +.m-xxxl { + margin: var(--c--theme--spacings--xxxl); +} + +.mb-xxxl { + margin-bottom: var(--c--theme--spacings--xxxl); +} + +.mt-xxxl { + margin-top: var(--c--theme--spacings--xxxl); +} + +.ml-xxxl { + margin-left: var(--c--theme--spacings--xxxl); +} + +.mr-xxxl { + margin-right: var(--c--theme--spacings--xxxl); +} + +.m-4xl { + margin: var(--c--theme--spacings--4xl); +} + +.mb-4xl { + margin-bottom: var(--c--theme--spacings--4xl); +} + +.mt-4xl { + margin-top: var(--c--theme--spacings--4xl); +} + +.ml-4xl { + margin-left: var(--c--theme--spacings--4xl); +} + +.mr-4xl { + margin-right: var(--c--theme--spacings--4xl); +} + +.m-5xl { + margin: var(--c--theme--spacings--5xl); +} + +.mb-5xl { + margin-bottom: var(--c--theme--spacings--5xl); +} + +.mt-5xl { + margin-top: var(--c--theme--spacings--5xl); +} + +.ml-5xl { + margin-left: var(--c--theme--spacings--5xl); +} + +.mr-5xl { + margin-right: var(--c--theme--spacings--5xl); +} + +.m-6xl { + margin: var(--c--theme--spacings--6xl); +} + +.mb-6xl { + margin-bottom: var(--c--theme--spacings--6xl); +} + +.mt-6xl { + margin-top: var(--c--theme--spacings--6xl); +} + +.ml-6xl { + margin-left: var(--c--theme--spacings--6xl); +} + +.mr-6xl { + margin-right: var(--c--theme--spacings--6xl); +} + +.m-7xl { + margin: var(--c--theme--spacings--7xl); +} + +.mb-7xl { + margin-bottom: var(--c--theme--spacings--7xl); +} + +.mt-7xl { + margin-top: var(--c--theme--spacings--7xl); +} + +.ml-7xl { + margin-left: var(--c--theme--spacings--7xl); +} + +.mr-7xl { + margin-right: var(--c--theme--spacings--7xl); +} + +.p-0 { + padding: var(--c--theme--spacings--0); +} + +.pb-0 { + padding-bottom: var(--c--theme--spacings--0); +} + +.pt-0 { + padding-top: var(--c--theme--spacings--0); +} + +.pl-0 { + padding-left: var(--c--theme--spacings--0); +} + +.pr-0 { + padding-right: var(--c--theme--spacings--0); +} + +.p-xl { + padding: var(--c--theme--spacings--xl); +} + +.pb-xl { + padding-bottom: var(--c--theme--spacings--xl); +} + +.pt-xl { + padding-top: var(--c--theme--spacings--xl); +} + +.pl-xl { + padding-left: var(--c--theme--spacings--xl); +} + +.pr-xl { + padding-right: var(--c--theme--spacings--xl); +} + +.p-l { + padding: var(--c--theme--spacings--l); +} + +.pb-l { + padding-bottom: var(--c--theme--spacings--l); +} + +.pt-l { + padding-top: var(--c--theme--spacings--l); +} + +.pl-l { + padding-left: var(--c--theme--spacings--l); +} + +.pr-l { + padding-right: var(--c--theme--spacings--l); +} + +.p-b { + padding: var(--c--theme--spacings--b); +} + +.pb-b { + padding-bottom: var(--c--theme--spacings--b); +} + +.pt-b { + padding-top: var(--c--theme--spacings--b); +} + +.pl-b { + padding-left: var(--c--theme--spacings--b); +} + +.pr-b { + padding-right: var(--c--theme--spacings--b); +} + +.p-s { + padding: var(--c--theme--spacings--s); +} + +.pb-s { + padding-bottom: var(--c--theme--spacings--s); +} + +.pt-s { + padding-top: var(--c--theme--spacings--s); +} + +.pl-s { + padding-left: var(--c--theme--spacings--s); +} + +.pr-s { + padding-right: var(--c--theme--spacings--s); +} + +.p-t { + padding: var(--c--theme--spacings--t); +} + +.pb-t { + padding-bottom: var(--c--theme--spacings--t); +} + +.pt-t { + padding-top: var(--c--theme--spacings--t); +} + +.pl-t { + padding-left: var(--c--theme--spacings--t); +} + +.pr-t { + padding-right: var(--c--theme--spacings--t); +} + +.p-st { + padding: var(--c--theme--spacings--st); +} + +.pb-st { + padding-bottom: var(--c--theme--spacings--st); +} + +.pt-st { + padding-top: var(--c--theme--spacings--st); +} + +.pl-st { + padding-left: var(--c--theme--spacings--st); +} + +.pr-st { + padding-right: var(--c--theme--spacings--st); +} + +.p-none { + padding: var(--c--theme--spacings--none); +} + +.pb-none { + padding-bottom: var(--c--theme--spacings--none); +} + +.pt-none { + padding-top: var(--c--theme--spacings--none); +} + +.pl-none { + padding-left: var(--c--theme--spacings--none); +} + +.pr-none { + padding-right: var(--c--theme--spacings--none); +} + +.p-auto { + padding: var(--c--theme--spacings--auto); +} + +.pb-auto { + padding-bottom: var(--c--theme--spacings--auto); +} + +.pt-auto { + padding-top: var(--c--theme--spacings--auto); +} + +.pl-auto { + padding-left: var(--c--theme--spacings--auto); +} + +.pr-auto { + padding-right: var(--c--theme--spacings--auto); +} + +.p-bx { + padding: var(--c--theme--spacings--bx); +} + +.pb-bx { + padding-bottom: var(--c--theme--spacings--bx); +} + +.pt-bx { + padding-top: var(--c--theme--spacings--bx); +} + +.pl-bx { + padding-left: var(--c--theme--spacings--bx); +} + +.pr-bx { + padding-right: var(--c--theme--spacings--bx); +} + +.p-full { + padding: var(--c--theme--spacings--full); +} + +.pb-full { + padding-bottom: var(--c--theme--spacings--full); +} + +.pt-full { + padding-top: var(--c--theme--spacings--full); +} + +.pl-full { + padding-left: var(--c--theme--spacings--full); +} + +.pr-full { + padding-right: var(--c--theme--spacings--full); +} + +.p-4xs { + padding: var(--c--theme--spacings--4xs); +} + +.pb-4xs { + padding-bottom: var(--c--theme--spacings--4xs); +} + +.pt-4xs { + padding-top: var(--c--theme--spacings--4xs); +} + +.pl-4xs { + padding-left: var(--c--theme--spacings--4xs); +} + +.pr-4xs { + padding-right: var(--c--theme--spacings--4xs); +} + +.p-3xs { + padding: var(--c--theme--spacings--3xs); +} + +.pb-3xs { + padding-bottom: var(--c--theme--spacings--3xs); +} + +.pt-3xs { + padding-top: var(--c--theme--spacings--3xs); +} + +.pl-3xs { + padding-left: var(--c--theme--spacings--3xs); +} + +.pr-3xs { + padding-right: var(--c--theme--spacings--3xs); +} + +.p-2xs { + padding: var(--c--theme--spacings--2xs); +} + +.pb-2xs { + padding-bottom: var(--c--theme--spacings--2xs); +} + +.pt-2xs { + padding-top: var(--c--theme--spacings--2xs); +} + +.pl-2xs { + padding-left: var(--c--theme--spacings--2xs); +} + +.pr-2xs { + padding-right: var(--c--theme--spacings--2xs); +} + +.p-xs { + padding: var(--c--theme--spacings--xs); +} + +.pb-xs { + padding-bottom: var(--c--theme--spacings--xs); +} + +.pt-xs { + padding-top: var(--c--theme--spacings--xs); +} + +.pl-xs { + padding-left: var(--c--theme--spacings--xs); +} + +.pr-xs { + padding-right: var(--c--theme--spacings--xs); +} + +.p-sm { + padding: var(--c--theme--spacings--sm); +} + +.pb-sm { + padding-bottom: var(--c--theme--spacings--sm); +} + +.pt-sm { + padding-top: var(--c--theme--spacings--sm); +} + +.pl-sm { + padding-left: var(--c--theme--spacings--sm); +} + +.pr-sm { + padding-right: var(--c--theme--spacings--sm); +} + +.p-base { + padding: var(--c--theme--spacings--base); +} + +.pb-base { + padding-bottom: var(--c--theme--spacings--base); +} + +.pt-base { + padding-top: var(--c--theme--spacings--base); +} + +.pl-base { + padding-left: var(--c--theme--spacings--base); +} + +.pr-base { + padding-right: var(--c--theme--spacings--base); +} + +.p-md { + padding: var(--c--theme--spacings--md); +} + +.pb-md { + padding-bottom: var(--c--theme--spacings--md); +} + +.pt-md { + padding-top: var(--c--theme--spacings--md); +} + +.pl-md { + padding-left: var(--c--theme--spacings--md); +} + +.pr-md { + padding-right: var(--c--theme--spacings--md); +} + +.p-lg { + padding: var(--c--theme--spacings--lg); +} + +.pb-lg { + padding-bottom: var(--c--theme--spacings--lg); +} + +.pt-lg { + padding-top: var(--c--theme--spacings--lg); +} + +.pl-lg { + padding-left: var(--c--theme--spacings--lg); +} + +.pr-lg { + padding-right: var(--c--theme--spacings--lg); +} + +.p-xxl { + padding: var(--c--theme--spacings--xxl); +} + +.pb-xxl { + padding-bottom: var(--c--theme--spacings--xxl); +} + +.pt-xxl { + padding-top: var(--c--theme--spacings--xxl); +} + +.pl-xxl { + padding-left: var(--c--theme--spacings--xxl); +} + +.pr-xxl { + padding-right: var(--c--theme--spacings--xxl); +} + +.p-xxxl { + padding: var(--c--theme--spacings--xxxl); +} + +.pb-xxxl { + padding-bottom: var(--c--theme--spacings--xxxl); +} + +.pt-xxxl { + padding-top: var(--c--theme--spacings--xxxl); +} + +.pl-xxxl { + padding-left: var(--c--theme--spacings--xxxl); +} + +.pr-xxxl { + padding-right: var(--c--theme--spacings--xxxl); +} + +.p-4xl { + padding: var(--c--theme--spacings--4xl); +} + +.pb-4xl { + padding-bottom: var(--c--theme--spacings--4xl); +} + +.pt-4xl { + padding-top: var(--c--theme--spacings--4xl); +} + +.pl-4xl { + padding-left: var(--c--theme--spacings--4xl); +} + +.pr-4xl { + padding-right: var(--c--theme--spacings--4xl); +} + +.p-5xl { + padding: var(--c--theme--spacings--5xl); +} + +.pb-5xl { + padding-bottom: var(--c--theme--spacings--5xl); +} + +.pt-5xl { + padding-top: var(--c--theme--spacings--5xl); +} + +.pl-5xl { + padding-left: var(--c--theme--spacings--5xl); +} + +.pr-5xl { + padding-right: var(--c--theme--spacings--5xl); +} + +.p-6xl { + padding: var(--c--theme--spacings--6xl); +} + +.pb-6xl { + padding-bottom: var(--c--theme--spacings--6xl); +} + +.pt-6xl { + padding-top: var(--c--theme--spacings--6xl); +} + +.pl-6xl { + padding-left: var(--c--theme--spacings--6xl); +} + +.pr-6xl { + padding-right: var(--c--theme--spacings--6xl); +} + +.p-7xl { + padding: var(--c--theme--spacings--7xl); +} + +.pb-7xl { + padding-bottom: var(--c--theme--spacings--7xl); +} + +.pt-7xl { + padding-top: var(--c--theme--spacings--7xl); +} + +.pl-7xl { + padding-left: var(--c--theme--spacings--7xl); +} + +.pr-7xl { + padding-right: var(--c--theme--spacings--7xl); +} diff --git a/src/frontend/apps/conversations/src/cunningham/cunningham-tokens.ts b/src/frontend/apps/conversations/src/cunningham/cunningham-tokens.ts new file mode 100644 index 0000000..a547d65 --- /dev/null +++ b/src/frontend/apps/conversations/src/cunningham/cunningham-tokens.ts @@ -0,0 +1,598 @@ +export const tokens = { + themes: { + default: { + theme: { + colors: { + 'secondary-text': '#fff', + 'secondary-100': '#fee9ea', + 'secondary-200': '#fedfdf', + 'secondary-300': '#fdbfbf', + 'secondary-400': '#e1020f', + 'secondary-500': '#c91a1f', + 'secondary-600': '#5e2b2b', + 'secondary-700': '#3b2424', + 'secondary-800': '#341f1f', + 'secondary-900': '#2b1919', + 'info-text': '#0078f3', + 'info-100': '#E8EDFF', + 'info-200': '#DDE5FF', + 'info-300': '#BCCDFF', + 'info-400': '#518FFF', + 'info-500': '#0078F3', + 'info-600': '#0063CB', + 'info-700': '#273961', + 'info-800': '#222A3F', + 'info-900': '#1D2437', + 'greyscale-100': '#eee', + 'greyscale-200': '#E5E5E5', + 'greyscale-300': '#CECECE', + 'greyscale-400': '#929292', + 'greyscale-500': '#7C7C7C', + 'greyscale-600': '#666666', + 'greyscale-700': '#3A3A3A', + 'greyscale-800': '#2A2A2A', + 'greyscale-900': '#242424', + 'greyscale-000': '#fff', + 'primary-100': '#ECECFE', + 'primary-200': '#E3E3FD', + 'primary-300': '#CACAFB', + 'primary-400': '#8585F6', + 'primary-500': '#6A6AF4', + 'primary-600': '#313178', + 'primary-700': '#272747', + 'primary-800': '#000091', + 'primary-900': '#21213F', + 'success-100': '#dffee6', + 'success-200': '#b8fec9', + 'success-300': '#88fdaa', + 'success-400': '#3bea7e', + 'success-500': '#1f8d49', + 'success-600': '#18753c', + 'success-700': '#204129', + 'success-800': '#1e2e22', + 'success-900': '#19281d', + 'warning-100': '#fff4f3', + 'warning-200': '#ffe9e6', + 'warning-300': '#ffded9', + 'warning-400': '#ffbeb4', + 'warning-500': '#d64d00', + 'warning-600': '#b34000', + 'warning-700': '#5e2c21', + 'warning-800': '#3e241e', + 'warning-900': '#361e19', + 'danger-100': '#FFE9E9', + 'danger-200': '#FFDDDD', + 'danger-300': '#FFBDBD', + 'danger-400': '#FF5655', + 'danger-500': '#F60700', + 'danger-600': '#CE0500', + 'danger-700': '#642626', + 'danger-800': '#412121', + 'danger-900': '#391C1C', + 'primary-text': '#000091', + 'success-text': '#1f8d49', + 'warning-text': '#d64d00', + 'danger-text': '#FFF', + 'primary-050': '#F5F5FE', + 'primary-150': '#F4F4FD', + 'greyscale-text': '#303C4B', + 'greyscale-050': '#F6F6F6', + 'greyscale-250': '#ddd', + 'greyscale-350': '#ddd', + 'greyscale-750': '#353535', + 'greyscale-950': '#1E1E1E', + 'greyscale-1000': '#161616', + 'danger-050': '#FFF4F4', + 'blue-500': '#417DC4', + 'brown-500': '#BD987A', + 'cyan-500': '#009099', + 'gold-500': '#C3992A', + 'green-500': '#00A95F', + 'olive-500': '#68A532', + 'orange-500': '#E4794A', + 'purple-500': '#A558A0', + 'red-500': '#E1000F', + 'yellow-500': '#B7A73F', + 'rose-500': '#E18B76', + 'primary-action': '#1212FF', + 'primary-bg': '#FAFAFA', + 'primary-focus': '#0A76F6', + 'secondary-icon': 'var(--c--theme--colors--primary-text)', + 'blue-400': '#7AB1E8', + 'blue-600': '#3558A2', + 'brown-400': '#E6BE92', + 'brown-600': '#745B47', + 'cyan-400': '#34BAB5', + 'cyan-600': '#006A6F', + 'gold-400': '#FFCA00', + 'gold-600': '#695240', + 'green-400': '#34CB6A', + 'green-600': '#297254', + 'olive-400': '#99C221', + 'olive-600': '#447049', + 'orange-400': '#FF732C', + 'orange-600': '#755348', + 'pink-400': '#FFB7AE', + 'pink-500': '#E18B76', + 'pink-600': '#8D533E', + 'purple-400': '#CE70CC', + 'purple-600': '#6E445A', + 'yellow-400': '#D8C634', + 'yellow-600': '#66673D', + }, + font: { + sizes: { + h1: '2rem', + h2: '1.75rem', + h3: '1.5rem', + h4: '1.375rem', + h5: '1.25rem', + h6: '1.125rem', + l: '1rem', + m: '0.8125rem', + s: '0.75rem', + xs: '0.75rem', + sm: '0.875rem', + md: '1rem', + lg: '1.125rem', + ml: '0.938rem', + xl: '1.25rem', + t: '0.6875rem', + 'xl-alt': '5rem', + 'lg-alt': '4.5rem', + 'md-alt': '4rem', + 'sm-alt': '3.5rem', + 'xs-alt': '3rem', + }, + weights: { + thin: 100, + light: 300, + regular: 400, + medium: 500, + bold: 600, + extrabold: 800, + black: 900, + }, + families: { base: 'Marianne', accent: 'Marianne' }, + letterSpacings: { + h1: 'normal', + h2: 'normal', + h3: 'normal', + h4: 'normal', + h5: '1px', + h6: 'normal', + l: 'normal', + m: 'normal', + s: 'normal', + }, + }, + spacings: { + '0': '0', + xl: '2.5rem', + l: '3rem', + b: '1.625rem', + s: '1rem', + t: '0.5rem', + st: '0.25rem', + none: '0', + auto: 'auto', + bx: '2.2rem', + full: '100%', + '4xs': '0.125rem', + '3xs': '0.25rem', + '2xs': '0.375rem', + xs: '0.5rem', + sm: '0.75rem', + base: '1rem', + md: '1.5rem', + lg: '2rem', + xxl: '3rem', + xxxl: '3.5rem', + '4xl': '4rem', + '5xl': '4.5rem', + '6xl': '6rem', + '7xl': '7.5rem', + }, + transitions: { + 'ease-in': 'cubic-bezier(0.32, 0, 0.67, 0)', + 'ease-out': 'cubic-bezier(0.33, 1, 0.68, 1)', + 'ease-in-out': 'cubic-bezier(0.65, 0, 0.35, 1)', + duration: '250ms', + }, + breakpoints: { + xs: '480px', + sm: '576px', + md: '768px', + lg: '992px', + xl: '1200px', + xxl: '1400px', + xxs: '320px', + mobile: '768px', + tablet: '1024px', + }, + logo: { src: '', alt: '', widthHeader: '', widthFooter: '' }, + }, + components: { + modal: { 'width-small': '342px' }, + tooltip: { padding: '4px 8px', 'background-color': '#161616' }, + button: { + 'medium-height': '40px', + 'medium-text-height': '40px', + 'border-radius': '4px', + 'small-height': '26px', + primary: { + 'background--color': '#000091', + 'background--color-hover': '#1212ff', + 'background--color-active': '#2323ff', + 'background--color-disabled': '#eee', + color: '#fff', + 'color-hover': '#fff', + 'color-active': '#fff', + 'color-focus-visible': '#fff', + disabled: '#7C7C7C', + }, + 'primary-text': { + 'background--color': '#000091', + 'background--color-hover': '#eee', + 'background--color-active': '#ECECFE', + 'background--color-focus-visible': '#fff', + 'background--color-disabled': '#fff', + color: '#000091', + 'color-hover': '#000091', + disabled: '#929292', + }, + secondary: { + 'background--color-hover': '#F6F6F6', + 'background--color-active': '#EDEDED', + 'background--color-focus-visible': '#fff', + 'background--disabled': '#fff', + color: '#000091', + 'border--color': '#CECECE', + 'border--color-hover': '#CECECE', + 'border--color-disabled': '#CECECE', + disabled: '#929292', + }, + tertiary: { + 'background--color': '#ECECFE', + 'background--color-focus-visible': '#ECECFE', + 'background--color-hover': '#CACAFB', + 'background--color-active': '#CACAFB', + 'background--disabled': '#F5F5FE', + color: '#000091', + disabled: '#CACAFB', + }, + 'tertiary-text': { + 'background--color-hover': '#eee', + 'color-hover': '#000091', + color: '#313178', + }, + danger: { + 'color-hover': 'white', + 'background--color': '#CE0500', + 'background--color-hover': '#FF2725', + 'background--color-focus-visible': '#CE0500', + 'background--color-disabled': '#eee', + 'color-disabled': '#929292', + }, + }, + datagrid: { + 'header--color': '#666666', + 'header--size': '12px', + 'header--weight': '500', + 'body--background-color-hover': '#eee', + }, + 'forms-checkbox': { + 'border-radius': '4px', + 'border-color': '#000091', + 'background-color--hover': '#eee', + 'border--color-disabled': '#E5E5E5', + 'border--color': '#000091', + 'background--disabled': '#E5E5E5', + 'background--enable': '#000091', + 'check--disabled': '#CECECE', + 'check--enable': '#fff', + color: '#000091', + 'label--color': '#161616', + 'label--size': '0.875rem', + 'label--weight': '500', + 'text--color': '#666666', + 'text--size': '0.75rem', + 'text--weight': '400', + 'text--color-disabled': '#CECECE', + }, + 'forms-labelledbox': { + 'label-color--small': '#1E1E1E', + 'label-color--small--disabled': '#CECECE', + 'label-color--big': '#1E1E1E', + 'label-color--big--disabled': '#CECECE', + }, + 'forms-radio': { + 'border-color': '#000091', + 'background-color': '#fff', + 'accent-color': '#000091', + 'accent-color-disabled': '#CECECE', + }, + 'forms-switch': { + 'border--color-disabled': '#CECECE', + 'border--color': '#000091', + 'handle-background-color': 'white', + 'handle-background-color--disabled': '#fff', + 'rail-background-color--disabled': '#fff', + 'accent-color': '#000091', + }, + 'forms-textarea': { + 'label-color--focus': '#161616', + 'border-radius': '4px', + 'border-color': '#929292', + 'box-shadow--color--hover': '#929292', + 'box-shadow--color--focus': '#000091', + 'value-color': '#1E1E1E', + 'value-color--disabled': '#CECECE', + 'font-size': '14px', + }, + 'forms-input': { + 'label-color--focus': '#161616', + 'border-radius': '4px', + 'border-color': '#929292', + 'box-shadow--color--hover': '#929292', + 'box-shadow--color--focus': '#000091', + 'value-color': '#1E1E1E', + 'value-color--disabled': '#CECECE', + 'font-size': '14px', + }, + 'forms-select': { + 'label-color--focus': '#161616', + 'item-font-size': '14px', + 'border-radius': '4px', + 'border-radius-hover': '4px', + 'border-color': '#929292', + 'box-shadow--color--hover': '#929292', + 'box-shadow--color--focus': '#000091', + 'value-color': '#1E1E1E', + 'font-size': '14px', + }, + badge: { + 'font-size': '0.75rem', + 'border-radius': '4px', + 'padding-inline': '0.5rem', + 'padding-block': '0.375rem', + accent: { 'background-color': '#ECECFE', color: '#313178' }, + neutral: { 'background-color': '#eee', color: '#666666' }, + danger: { 'background-color': '#FFE9E9', color: '#CE0500' }, + success: { 'background-color': '#dffee6', color: '#18753c' }, + warning: { 'background-color': '#fff4f3', color: '#b34000' }, + info: { 'background-color': '#E8EDFF', color: '#0063CB' }, + }, + 'la-gaufre': false, + 'home-proconnect': false, + beta: false, + 'image-system-filter': '', + favicon: { + ico: '/assets/favicon-light.ico', + 'png-light': '/assets/favicon-light.png', + 'png-dark': '/assets/favicon-dark.png', + }, + }, + }, + dark: { + theme: { + colors: { + 'greyscale-100': '#182536', + 'greyscale-200': '#303C4B', + 'greyscale-300': '#555F6B', + 'greyscale-400': '#79818A', + 'greyscale-500': '#9EA3AA', + 'greyscale-600': '#C2C6CA', + 'greyscale-700': '#E7E8EA', + 'greyscale-800': '#F3F4F4', + 'greyscale-900': '#FAFAFB', + 'greyscale-000': '#0C1A2B', + 'primary-100': '#3B4C62', + 'primary-200': '#4D6481', + 'primary-300': '#6381A6', + 'primary-400': '#7FA5D5', + 'primary-500': '#8CB5EA', + 'primary-600': '#A3C4EE', + 'primary-700': '#C3D8F4', + 'primary-800': '#DDE9F8', + 'primary-900': '#F4F8FD', + 'success-100': '#EEF8D7', + 'success-200': '#D9F1B2', + 'success-300': '#BDE985', + 'success-400': '#A0E25D', + 'success-500': '#76D628', + 'success-600': '#5BB520', + 'success-700': '#43941A', + 'success-800': '#307414', + 'success-900': '#225D10', + 'warning-100': '#F7F3D5', + 'warning-200': '#F0E5AA', + 'warning-300': '#E8D680', + 'warning-400': '#E3C95F', + 'warning-500': '#D9B32B', + 'warning-600': '#BD9721', + 'warning-700': '#9D7B1C', + 'warning-800': '#7E6016', + 'warning-900': '#684D12', + 'danger-100': '#F8D0D0', + 'danger-200': '#F09898', + 'danger-300': '#F09898', + 'danger-400': '#ED8585', + 'danger-500': '#E96666', + 'danger-600': '#DD6666', + 'danger-700': '#C36666', + 'danger-800': '#AE6666', + 'danger-900': '#9D6666', + }, + }, + }, + dsfr: { + theme: { + colors: { 'secondary-icon': '#C9191E' }, + logo: { + src: '/assets/logo-gouv.svg', + widthHeader: '110px', + widthFooter: '220px', + alt: 'Gouvernement Logo', + }, + }, + components: { + 'la-gaufre': true, + 'home-proconnect': true, + beta: true, + favicon: { + ico: '/assets/favicon-dsfr.ico', + 'png-light': '/assets/favicon-dsfr.png', + 'png-dark': '/assets/favicon-dark-dsfr.png', + }, + }, + }, + generic: { + theme: { + colors: { + 'primary-action': '#206EBD', + 'primary-focus': '#1E64BF', + 'primary-text': '#2E2C28', + 'primary-050': '#F8F8F7', + 'primary-100': '#F0EFEC', + 'primary-150': '#F4F4FD', + 'primary-200': '#E8E7E4', + 'primary-300': '#CFCDC9', + 'primary-400': '#979592', + 'primary-500': '#82807D', + 'primary-600': '#3F3D39', + 'primary-700': '#2E2C28', + 'primary-800': '#302E29', + 'primary-900': '#282622', + 'primary-950': '#201F1C', + 'secondary-text': '#fff', + 'secondary-50': '#F4F7FA', + 'secondary-100': '#D7E3EE', + 'secondary-200': '#B8CCE1', + 'secondary-300': '#99B4D3', + 'secondary-400': '#7595BE', + 'secondary-500': '#5874A0', + 'secondary-600': '#3A5383', + 'secondary-700': '#1E3462', + 'secondary-800': '#091B41', + 'secondary-900': '#08183B', + 'secondary-950': '#071636', + 'greyscale-text': '#3C3B38', + 'greyscale-000': '#fff', + 'greyscale-050': '#F8F7F7', + 'greyscale-100': '#F3F3F2', + 'greyscale-200': '#ECEBEA', + 'greyscale-250': '#E4E3E2', + 'greyscale-300': '#D3D2CF', + 'greyscale-350': '#eee', + 'greyscale-400': '#96948E', + 'greyscale-500': '#817E77', + 'greyscale-600': '#6A6862', + 'greyscale-700': '#3C3B38', + 'greyscale-750': '#383632', + 'greyscale-800': '#2D2B27', + 'greyscale-900': '#262522', + 'greyscale-950': '#201F1C', + 'greyscale-1000': '#181714', + 'success-text': '#234935', + 'success-50': '#F3FBF5', + 'success-100': '#E4F7EA', + 'success-200': '#CAEED4', + 'success-300': '#A0E0B5', + 'success-400': '#6CC88C', + 'success-500': '#6CC88C', + 'success-600': '#358D5C', + 'success-700': '#2D704B', + 'success-800': '#28583F', + 'success-900': '#234935', + 'success-950': '#0F281B', + 'info-text': '#212445', + 'info-50': '#F2F6FB', + 'info-100': '#E2E9F5', + 'info-200': '#CCD8EE', + 'info-300': '#A9C0E3', + 'info-400': '#809DD4', + 'info-500': '#617BC7', + 'info-600': '#4A5CBF', + 'info-700': '#3E49B2', + 'info-800': '#353C8F', + 'info-900': '#303771', + 'info-950': '#212445', + 'warning-text': '#D97C3A', + 'warning-50': '#FDF7F1', + 'warning-100': '#FBEDDC', + 'warning-200': '#F5D9B9', + 'warning-300': '#EDBE8C', + 'warning-400': '#E2985C', + 'warning-500': '#D97C3A', + 'warning-600': '#C96330', + 'warning-700': '#A34B32', + 'warning-800': '#813B2C', + 'warning-900': '#693327', + 'warning-950': '#381713', + 'danger-action': '#C0182A', + 'danger-text': '#FFF', + 'danger-050': '#FDF5F4', + 'danger-100': '#FBEBE8', + 'danger-200': '#F9E0DC', + 'danger-300': '#F3C3BD', + 'danger-400': '#E26552', + 'danger-500': '#C91F00', + 'danger-600': '#A71901', + 'danger-700': '#562C2B', + 'danger-800': '#392425', + 'danger-900': '#311F20', + 'danger-950': '#2A191A', + 'blue-400': '#8BAECC', + 'blue-500': '#567AA2', + 'blue-600': '#455784', + 'brown-400': '#E4C090', + 'brown-500': '#BA9977', + 'brown-600': '#735C45', + 'cyan-400': '#5CBEC9', + 'cyan-500': '#43A1B3', + 'cyan-600': '#39809B', + 'gold-400': '#ECBF50', + 'gold-500': '#DFA038', + 'gold-600': '#C17B31', + 'green-400': '#5DBD9A', + 'green-500': '#3AA183', + 'green-600': '#2A816D', + 'olive-400': '#AFD662', + 'olive-500': '#90BB4B', + 'olive-600': '#6E9441', + 'orange-400': '#E2985C', + 'orange-500': '#D97C3A', + 'orange-600': '#C96330', + 'pink-400': '#BE8FC8', + 'pink-500': '#A563B1', + 'pink-600': '#8B44A5', + 'purple-400': '#BE8FC8', + 'purple-500': '#A563B1', + 'purple-600': '#8B44A5', + 'yellow-400': '#EDC947', + 'yellow-500': '#DBB13A', + 'yellow-600': '#B88A34', + }, + font: { + families: { + base: 'Inter, Roboto Flex Variable, sans-serif', + accent: 'Inter, Roboto Flex Variable, sans-serif', + }, + }, + }, + components: { + button: { + primary: { + background: { + 'color-hover': 'var(--c--theme--colors--primary-focus)', + 'color-active': 'var(--c--theme--colors--primary-focus)', + 'color-focus': 'var(--c--theme--colors--primary-focus)', + }, + }, + }, + 'image-system-filter': 'saturate(0.2)', + }, + }, + }, +}; diff --git a/src/frontend/apps/conversations/src/cunningham/index.ts b/src/frontend/apps/conversations/src/cunningham/index.ts new file mode 100644 index 0000000..1e5bd52 --- /dev/null +++ b/src/frontend/apps/conversations/src/cunningham/index.ts @@ -0,0 +1,2 @@ +export * from './cunningham-tokens'; +export * from './useCunninghamTheme'; diff --git a/src/frontend/apps/conversations/src/cunningham/useCunninghamTheme.tsx b/src/frontend/apps/conversations/src/cunningham/useCunninghamTheme.tsx new file mode 100644 index 0000000..7517eba --- /dev/null +++ b/src/frontend/apps/conversations/src/cunningham/useCunninghamTheme.tsx @@ -0,0 +1,61 @@ +import merge from 'lodash/merge'; +import { create } from 'zustand'; + +import { tokens } from './cunningham-tokens'; + +type Tokens = typeof tokens.themes.default & + Partial<(typeof tokens.themes)[keyof typeof tokens.themes]>; +type ColorsTokens = Tokens['theme']['colors']; +type FontSizesTokens = Tokens['theme']['font']['sizes']; +type SpacingsTokens = Tokens['theme']['spacings']; +type ComponentTokens = Tokens['components'] & { + alpha?: boolean; + beta?: boolean; +}; +export type Theme = keyof typeof tokens.themes; + +interface ThemeStore { + colorsTokens: Partial; + componentTokens: ComponentTokens; + currentTokens: Partial; + fontSizesTokens: Partial; + setTheme: (theme: Theme) => void; + spacingsTokens: Partial; + theme: Theme; + themeTokens: Partial; +} + +const getMergedTokens = (theme: Theme) => { + return merge({}, tokens.themes['default'], tokens.themes[theme]); +}; + +const DEFAULT_THEME: Theme = 'generic'; +const defaultTokens = getMergedTokens(DEFAULT_THEME); + +const initialState: ThemeStore = { + colorsTokens: defaultTokens.theme.colors, + componentTokens: defaultTokens.components, + currentTokens: tokens.themes[DEFAULT_THEME] as Partial, + fontSizesTokens: defaultTokens.theme.font.sizes, + setTheme: () => {}, + spacingsTokens: defaultTokens.theme.spacings, + theme: DEFAULT_THEME, + themeTokens: defaultTokens.theme, +}; + +export const useCunninghamTheme = create((set) => ({ + ...initialState, + setTheme: (theme: Theme) => { + const newTokens = getMergedTokens(theme); + + set({ + colorsTokens: newTokens.theme.colors, + componentTokens: newTokens.components, + currentTokens: tokens.themes[theme] as Partial, + fontSizesTokens: newTokens.theme.font.sizes, + spacingsTokens: newTokens.theme.spacings, + theme, + themeTokens: newTokens.theme, + }); + }, +})); diff --git a/src/frontend/apps/conversations/src/custom-next.d.ts b/src/frontend/apps/conversations/src/custom-next.d.ts new file mode 100644 index 0000000..629ca84 --- /dev/null +++ b/src/frontend/apps/conversations/src/custom-next.d.ts @@ -0,0 +1,31 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ + +declare module '*.svg' { + import * as React from 'react'; + + const ReactComponent: React.FunctionComponent< + React.SVGProps & { + title?: string; + } + >; + + export default ReactComponent; +} + +declare module '*.svg?url' { + const content: { + src: string; + width: number; + height: number; + blurWidth: number; + blurHeight: number; + }; + export default content; +} + +namespace NodeJS { + interface ProcessEnv { + NEXT_PUBLIC_PRODUCT_NAME?: string; + NEXT_PUBLIC_API_ORIGIN?: string; + } +} diff --git a/src/frontend/apps/conversations/src/features/auth/__tests__/utils.test.tsx b/src/frontend/apps/conversations/src/features/auth/__tests__/utils.test.tsx new file mode 100644 index 0000000..2aa0a02 --- /dev/null +++ b/src/frontend/apps/conversations/src/features/auth/__tests__/utils.test.tsx @@ -0,0 +1,40 @@ +import { Crisp } from 'crisp-sdk-web'; +import fetchMock from 'fetch-mock'; + +import { gotoLogout } from '../utils'; + +jest.mock('crisp-sdk-web', () => ({ + ...jest.requireActual('crisp-sdk-web'), + Crisp: { + isCrispInjected: jest.fn().mockReturnValue(true), + setTokenId: jest.fn(), + user: { + setEmail: jest.fn(), + }, + session: { + reset: jest.fn(), + }, + }, +})); + +describe('utils', () => { + afterEach(() => { + jest.clearAllMocks(); + fetchMock.restore(); + }); + + it('checks support session is terminated when logout', () => { + window.$crisp = true; + Object.defineProperty(window, 'location', { + value: { + ...window.location, + replace: jest.fn(), + }, + writable: true, + }); + + gotoLogout(); + + expect(Crisp.session.reset).toHaveBeenCalled(); + }); +}); diff --git a/src/frontend/apps/conversations/src/features/auth/api/index.ts b/src/frontend/apps/conversations/src/features/auth/api/index.ts new file mode 100644 index 0000000..ce8db5d --- /dev/null +++ b/src/frontend/apps/conversations/src/features/auth/api/index.ts @@ -0,0 +1,2 @@ +export * from './useAuthQuery'; +export * from './types'; diff --git a/src/frontend/apps/conversations/src/features/auth/api/types.ts b/src/frontend/apps/conversations/src/features/auth/api/types.ts new file mode 100644 index 0000000..680329d --- /dev/null +++ b/src/frontend/apps/conversations/src/features/auth/api/types.ts @@ -0,0 +1,15 @@ +/** + * Represents user retrieved from the API. + * @interface User + * @property {string} id - The id of the user. + * @property {string} email - The email of the user. + * @property {string} name - The name of the user. + * @property {string} language - The language of the user. e.g. 'en-us', 'fr-fr', 'de-de'. + */ +export interface User { + id: string; + email: string; + full_name: string; + short_name: string; + language?: string; +} diff --git a/src/frontend/apps/conversations/src/features/auth/api/useAuthQuery.tsx b/src/frontend/apps/conversations/src/features/auth/api/useAuthQuery.tsx new file mode 100644 index 0000000..026beec --- /dev/null +++ b/src/frontend/apps/conversations/src/features/auth/api/useAuthQuery.tsx @@ -0,0 +1,39 @@ +import { UseQueryOptions, useQuery } from '@tanstack/react-query'; + +import { APIError, errorCauses, fetchAPI } from '@/api'; + +import { User } from './types'; + +/** + * Asynchronously retrieves the current user's data from the API. + * This function is called during frontend initialization to check + * the user's authentication status through a session cookie. + * + * @async + * @function getMe + * @throws {Error} Throws an error if the API request fails. + * @returns {Promise} A promise that resolves to the user data. + */ +export const getMe = async (): Promise => { + const response = await fetchAPI(`users/me/`); + if (!response.ok) { + throw new APIError( + `Couldn't fetch user data: ${response.statusText}`, + await errorCauses(response), + ); + } + return response.json() as Promise; +}; + +export const KEY_AUTH = 'auth'; + +export function useAuthQuery( + queryConfig?: UseQueryOptions, +) { + return useQuery({ + queryKey: [KEY_AUTH], + queryFn: getMe, + staleTime: 1000 * 60 * 15, // 15 minutes + ...queryConfig, + }); +} diff --git a/src/frontend/apps/conversations/src/features/auth/assets/button-proconnect.svg b/src/frontend/apps/conversations/src/features/auth/assets/button-proconnect.svg new file mode 100644 index 0000000..10d1f49 --- /dev/null +++ b/src/frontend/apps/conversations/src/features/auth/assets/button-proconnect.svg @@ -0,0 +1,40 @@ + + + + + + + + + + diff --git a/src/frontend/apps/conversations/src/features/auth/components/Auth.tsx b/src/frontend/apps/conversations/src/features/auth/components/Auth.tsx new file mode 100644 index 0000000..addb481 --- /dev/null +++ b/src/frontend/apps/conversations/src/features/auth/components/Auth.tsx @@ -0,0 +1,71 @@ +import { Loader } from '@openfun/cunningham-react'; +import { useRouter } from 'next/router'; +import { PropsWithChildren } from 'react'; + +import { Box } from '@/components'; +import { useConfig } from '@/core'; + +import { HOME_URL } from '../conf'; +import { useAuth } from '../hooks'; +import { getAuthUrl, gotoLogin } from '../utils'; + +export const Auth = ({ children }: PropsWithChildren) => { + const { isLoading, pathAllowed, isFetchedAfterMount, authenticated } = + useAuth(); + const { replace, pathname } = useRouter(); + const { data: config } = useConfig(); + + if (isLoading && !isFetchedAfterMount) { + return ( + + + + ); + } + + /** + * If the user is authenticated and wanted initially to access a document, + * we redirect to the document page. + */ + if (authenticated) { + const authUrl = getAuthUrl(); + if (authUrl) { + void replace(authUrl); + return ( + + + + ); + } + } + + /** + * If the user is not authenticated and the path is not allowed, we redirect to the login page. + */ + if (!authenticated && !pathAllowed) { + if (config?.FRONTEND_HOMEPAGE_FEATURE_ENABLED) { + void replace(HOME_URL); + } else { + gotoLogin(); + } + return ( + + + + ); + } + + /** + * If the user is authenticated and the path is the home page, we redirect to the index. + */ + if (pathname === HOME_URL && authenticated) { + void replace('/'); + return ( + + + + ); + } + + return children; +}; diff --git a/src/frontend/apps/conversations/src/features/auth/components/ButtonLogin.tsx b/src/frontend/apps/conversations/src/features/auth/components/ButtonLogin.tsx new file mode 100644 index 0000000..e3c123d --- /dev/null +++ b/src/frontend/apps/conversations/src/features/auth/components/ButtonLogin.tsx @@ -0,0 +1,59 @@ +import { Button } from '@openfun/cunningham-react'; +import { useTranslation } from 'react-i18next'; +import { css } from 'styled-components'; + +import { BoxButton } from '@/components'; + +import ProConnectImg from '../assets/button-proconnect.svg'; +import { useAuth } from '../hooks'; +import { gotoLogin, gotoLogout } from '../utils'; + +export const ButtonLogin = () => { + const { t } = useTranslation(); + const { authenticated } = useAuth(); + + if (!authenticated) { + return ( + + ); + } + + return ( + + ); +}; + +export const ProConnectButton = () => { + const { t } = useTranslation(); + + return ( + gotoLogin()} + aria-label={t('Proconnect Login')} + $css={css` + background-color: var(--c--theme--colors--primary-text); + &:hover { + background-color: var(--c--theme--colors--primary-action); + } + `} + $radius="4px" + className="--docs--proconnect-button" + > + + + ); +}; diff --git a/src/frontend/apps/conversations/src/features/auth/components/index.ts b/src/frontend/apps/conversations/src/features/auth/components/index.ts new file mode 100644 index 0000000..17f3a90 --- /dev/null +++ b/src/frontend/apps/conversations/src/features/auth/components/index.ts @@ -0,0 +1,2 @@ +export * from './Auth'; +export * from './ButtonLogin'; diff --git a/src/frontend/apps/conversations/src/features/auth/conf.ts b/src/frontend/apps/conversations/src/features/auth/conf.ts new file mode 100644 index 0000000..c44fe01 --- /dev/null +++ b/src/frontend/apps/conversations/src/features/auth/conf.ts @@ -0,0 +1,6 @@ +import { baseApiUrl } from '@/api'; + +export const HOME_URL = '/home'; +export const LOGIN_URL = `${baseApiUrl()}authenticate/`; +export const LOGOUT_URL = `${baseApiUrl()}logout/`; +export const PATH_AUTH_LOCAL_STORAGE = 'docs-path-auth'; diff --git a/src/frontend/apps/conversations/src/features/auth/hooks/__tests__/useAuth.test.tsx b/src/frontend/apps/conversations/src/features/auth/hooks/__tests__/useAuth.test.tsx new file mode 100644 index 0000000..56c56df --- /dev/null +++ b/src/frontend/apps/conversations/src/features/auth/hooks/__tests__/useAuth.test.tsx @@ -0,0 +1,81 @@ +import { renderHook, waitFor } from '@testing-library/react'; +import fetchMock from 'fetch-mock'; +import { Fragment } from 'react'; + +import { AbstractAnalytic } from '@/libs'; +import { AppWrapper } from '@/tests/utils'; + +import { useAuth } from '../useAuth'; + +const trackEventMock = jest.fn(); +const flag = true; +class TestAnalytic extends AbstractAnalytic { + public constructor() { + super(); + } + + public Provider() { + return ; + } + + public trackEvent(props: any) { + trackEventMock(props); + } + + public isFeatureFlagActivated(flagName: string): boolean { + if (flagName === 'CopyAsHTML') { + return flag; + } + + return true; + } +} + +jest.mock('next/router', () => ({ + ...jest.requireActual('next/router'), + useRouter: () => ({ + pathname: '/dashboard', + replace: jest.fn(), + }), +})); + +const dummyUser = { id: '123', email: 'test@example.com' }; + +describe('useAuth hook - trackEvent effect', () => { + beforeEach(() => { + jest.clearAllMocks(); + fetchMock.restore(); + }); + + test('calls trackEvent when user exists, isSuccess is true, and event was not tracked yet', async () => { + new TestAnalytic(); + + fetchMock.get('http://test.jest/api/v1.0/users/me/', { + body: JSON.stringify(dummyUser), + }); + + renderHook(() => useAuth(), { + wrapper: AppWrapper, + }); + + await waitFor(() => { + expect(trackEventMock).toHaveBeenCalledWith({ + eventName: 'user', + id: dummyUser.id, + email: dummyUser.email, + }); + }); + }); + + test('does not call trackEvent if already tracked', () => { + fetchMock.get('http://test.jest/api/v1.0/users/me/', { + body: JSON.stringify(dummyUser), + }); + + renderHook(() => useAuth(), { + wrapper: AppWrapper, + }); + + expect(trackEventMock).not.toHaveBeenCalled(); + }); +}); diff --git a/src/frontend/apps/conversations/src/features/auth/hooks/index.ts b/src/frontend/apps/conversations/src/features/auth/hooks/index.ts new file mode 100644 index 0000000..d9ae720 --- /dev/null +++ b/src/frontend/apps/conversations/src/features/auth/hooks/index.ts @@ -0,0 +1 @@ +export * from './useAuth'; diff --git a/src/frontend/apps/conversations/src/features/auth/hooks/useAuth.tsx b/src/frontend/apps/conversations/src/features/auth/hooks/useAuth.tsx new file mode 100644 index 0000000..8dfe191 --- /dev/null +++ b/src/frontend/apps/conversations/src/features/auth/hooks/useAuth.tsx @@ -0,0 +1,45 @@ +import { useRouter } from 'next/router'; +import { useEffect, useState } from 'react'; + +import { useAnalytics } from '@/libs'; + +import { useAuthQuery } from '../api'; + +const regexpUrlsAuth = [ + /\/chat\/$/g, // New conversation requires authentication + /\/chat$/g, + /\/chat\/.+$/g, // Conversation requires authentication: remove to allow anonymous access + /^\/$/g, // Root requires authentication +]; + +export const useAuth = () => { + const { data: user, ...authStates } = useAuthQuery(); + const { pathname } = useRouter(); + const { trackEvent } = useAnalytics(); + const [hasTracked, setHasTracked] = useState(authStates.isFetched); + const [pathAllowed, setPathAllowed] = useState( + !regexpUrlsAuth.some((regexp) => !!pathname.match(regexp)), + ); + + useEffect(() => { + setPathAllowed(!regexpUrlsAuth.some((regexp) => !!pathname.match(regexp))); + }, [pathname]); + + useEffect(() => { + if (!hasTracked && user && authStates.isSuccess) { + trackEvent({ + eventName: 'user', + id: user?.id || '', + email: user?.email || '', + }); + setHasTracked(true); + } + }, [hasTracked, authStates.isSuccess, user, trackEvent]); + + return { + user, + authenticated: !!user && authStates.isSuccess, + pathAllowed, + ...authStates, + }; +}; diff --git a/src/frontend/apps/conversations/src/features/auth/index.ts b/src/frontend/apps/conversations/src/features/auth/index.ts new file mode 100644 index 0000000..c044307 --- /dev/null +++ b/src/frontend/apps/conversations/src/features/auth/index.ts @@ -0,0 +1,5 @@ +export * from './api'; +export * from './components'; +export * from './conf'; +export * from './hooks'; +export * from './utils'; diff --git a/src/frontend/apps/conversations/src/features/auth/utils.ts b/src/frontend/apps/conversations/src/features/auth/utils.ts new file mode 100644 index 0000000..41d50cf --- /dev/null +++ b/src/frontend/apps/conversations/src/features/auth/utils.ts @@ -0,0 +1,30 @@ +import { terminateCrispSession } from '@/services/Crisp'; + +import { LOGIN_URL, LOGOUT_URL, PATH_AUTH_LOCAL_STORAGE } from './conf'; + +export const getAuthUrl = () => { + const path_auth = localStorage.getItem(PATH_AUTH_LOCAL_STORAGE); + if (path_auth) { + localStorage.removeItem(PATH_AUTH_LOCAL_STORAGE); + return path_auth; + } +}; + +export const setAuthUrl = () => { + if (window.location.pathname !== '/') { + localStorage.setItem(PATH_AUTH_LOCAL_STORAGE, window.location.pathname); + } +}; + +export const gotoLogin = (withRedirect = true) => { + if (withRedirect) { + setAuthUrl(); + } + + window.location.replace(LOGIN_URL); +}; + +export const gotoLogout = () => { + terminateCrispSession(); + window.location.replace(LOGOUT_URL); +}; diff --git a/src/frontend/apps/conversations/src/features/chat-search/assets/illustration-docs-empty.png b/src/frontend/apps/conversations/src/features/chat-search/assets/illustration-docs-empty.png new file mode 100644 index 0000000..c3dbb6e Binary files /dev/null and b/src/frontend/apps/conversations/src/features/chat-search/assets/illustration-docs-empty.png differ diff --git a/src/frontend/apps/conversations/src/features/chat-search/components/ConversationSearchItem.tsx b/src/frontend/apps/conversations/src/features/chat-search/components/ConversationSearchItem.tsx new file mode 100644 index 0000000..a10a8f6 --- /dev/null +++ b/src/frontend/apps/conversations/src/features/chat-search/components/ConversationSearchItem.tsx @@ -0,0 +1,38 @@ +import { Box, Icon } from '@/components'; +import { QuickSearchItemContent } from '@/components/quick-search/'; +import { ChatConversation } from '@/features/chat/types'; +import { SimpleConversationItem } from '@/features/left-panel/components/SimpleConversationItem'; +import { useResponsiveStore } from '@/stores'; + +type ConversationSearchItemProps = { + conversation: ChatConversation; +}; + +export const ConversationSearchItem = ({ + conversation, +}: ConversationSearchItemProps) => { + const { isDesktop } = useResponsiveStore(); + return ( + + + + + + + } + right={ + + } + /> + + ); +}; diff --git a/src/frontend/apps/conversations/src/features/chat-search/components/ConversationSearchModal.tsx b/src/frontend/apps/conversations/src/features/chat-search/components/ConversationSearchModal.tsx new file mode 100644 index 0000000..11b6117 --- /dev/null +++ b/src/frontend/apps/conversations/src/features/chat-search/components/ConversationSearchModal.tsx @@ -0,0 +1,114 @@ +import { Modal, ModalSize } from '@openfun/cunningham-react'; +import Image from 'next/image'; +import { useRouter } from 'next/navigation'; +import { useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { InView } from 'react-intersection-observer'; +import { useDebouncedCallback } from 'use-debounce'; + +import { Box } from '@/components'; +import { + QuickSearch, + QuickSearchData, + QuickSearchGroup, +} from '@/components/quick-search'; +import { useInfiniteConversations } from '@/features/chat/api/useConversations'; +import { ChatConversation } from '@/features/chat/types'; +import { useResponsiveStore } from '@/stores'; + +import EmptySearchIcon from '../assets/illustration-docs-empty.png'; + +import { ConversationSearchItem } from './ConversationSearchItem'; + +type ConversationSearchModalProps = { + onClose: () => void; + isOpen: boolean; +}; + +export const ConversationSearchModal = ({ + ...modalProps +}: ConversationSearchModalProps) => { + const { t } = useTranslation(); + const router = useRouter(); + const [search, setSearch] = useState(''); + const { isDesktop } = useResponsiveStore(); + const { + data, + isFetching, + isRefetching, + isLoading, + fetchNextPage, + hasNextPage, + } = useInfiniteConversations({ + page: 1, + title: search, + }); + const loading = isFetching || isRefetching || isLoading; + const handleInputSearch = useDebouncedCallback(setSearch, 300); + + const handleSelect = (conversation: ChatConversation) => { + router.push(`/chat/${conversation.id}`); + modalProps.onClose?.(); + }; + + const conversationsData: QuickSearchData = useMemo(() => { + const conversations = data?.pages.flatMap((page) => page.results) || []; + + return { + groupName: conversations.length > 0 ? t('Select a conversation') : '', + elements: search ? conversations : [], + emptyString: t('No conversation found'), + endActions: hasNextPage + ? [{ content: void fetchNextPage()} /> }] + : [], + }; + }, [data, hasNextPage, fetchNextPage, t, search]); + + return ( + + + + + {search.length === 0 && ( + + {t('No + + )} + {search && ( + ( + + )} + /> + )} + + + + + ); +}; diff --git a/src/frontend/apps/conversations/src/features/chat-search/components/index.ts b/src/frontend/apps/conversations/src/features/chat-search/components/index.ts new file mode 100644 index 0000000..e2720f8 --- /dev/null +++ b/src/frontend/apps/conversations/src/features/chat-search/components/index.ts @@ -0,0 +1 @@ +export * from './ConversationSearchModal'; diff --git a/src/frontend/apps/conversations/src/features/chat-search/index.ts b/src/frontend/apps/conversations/src/features/chat-search/index.ts new file mode 100644 index 0000000..07635cb --- /dev/null +++ b/src/frontend/apps/conversations/src/features/chat-search/index.ts @@ -0,0 +1 @@ +export * from './components'; diff --git a/src/frontend/apps/conversations/src/features/chat/api/useChat.tsx b/src/frontend/apps/conversations/src/features/chat/api/useChat.tsx new file mode 100644 index 0000000..be53da7 --- /dev/null +++ b/src/frontend/apps/conversations/src/features/chat/api/useChat.tsx @@ -0,0 +1,26 @@ +import { UseChatOptions, useChat as useAiSdkChat } from '@ai-sdk/react'; + +import { fetchAPI } from '@/api'; + +// Adapter to match the global fetch signature +const fetchAPIAdapter = (input: RequestInfo | URL, init?: RequestInit) => { + let url: string; + if (typeof input === 'string') { + url = input; + } else if (input instanceof URL) { + url = input.toString(); + } else if (input instanceof Request) { + url = input.url; + } else { + throw new Error('Unsupported input type for fetchAPIAdapter'); + } + return fetchAPI(url, init); +}; + +export function useChat(options: Omit) { + return useAiSdkChat({ + ...options, + maxSteps: 3, + fetch: fetchAPIAdapter, + }); +} diff --git a/src/frontend/apps/conversations/src/features/chat/api/useConversation.tsx b/src/frontend/apps/conversations/src/features/chat/api/useConversation.tsx new file mode 100644 index 0000000..0c208d9 --- /dev/null +++ b/src/frontend/apps/conversations/src/features/chat/api/useConversation.tsx @@ -0,0 +1,42 @@ +import { UseQueryOptions, useQuery } from '@tanstack/react-query'; + +import { APIError, errorCauses, fetchAPI } from '@/api'; +import { ChatConversation } from '@/features/chat/types'; + +export type ConversationsParams = { + id: string; +}; + +type ConversationResponse = ChatConversation; + +export const getConversation = async ({ + id, +}: ConversationsParams): Promise => { + const response = await fetchAPI(`chats/${id}/`); + + if (!response.ok) { + throw new APIError( + 'Failed to get the conversation', + await errorCauses(response), + ); + } + + return response.json() as Promise; +}; + +export const KEY_CONVERSATION = 'conversation'; + +export function useConversations( + param: ConversationsParams, + queryConfig?: UseQueryOptions< + ConversationResponse, + APIError, + ConversationResponse + >, +) { + return useQuery({ + queryKey: [KEY_CONVERSATION, param], + queryFn: () => getConversation(param), + ...queryConfig, + }); +} diff --git a/src/frontend/apps/conversations/src/features/chat/api/useConversations.tsx b/src/frontend/apps/conversations/src/features/chat/api/useConversations.tsx new file mode 100644 index 0000000..57b7e98 --- /dev/null +++ b/src/frontend/apps/conversations/src/features/chat/api/useConversations.tsx @@ -0,0 +1,76 @@ +import { UseQueryOptions, useQuery } from '@tanstack/react-query'; + +import { + APIError, + APIList, + errorCauses, + fetchAPI, + useAPIInfiniteQuery, +} from '@/api'; +import { ChatConversation } from '@/features/chat/types'; + +const _conversationsOrdering = [ + 'created_at', + '-created_at', + 'updated_at', + '-updated_at', +] as const; + +export type ConversationsOrdering = (typeof _conversationsOrdering)[number]; + +export type ConversationsParams = { + page: number; + ordering?: ConversationsOrdering; + title?: string; +}; + +export type ConversationsResponse = APIList; + +export const getConversations = async ( + params: ConversationsParams, +): Promise => { + const searchParams = new URLSearchParams(); + if (params.page) { + searchParams.set('page', params.page.toString()); + } + + if (params.ordering) { + searchParams.set('ordering', params.ordering); + } + + if (params.title) { + searchParams.set('title', params.title); + } + + const response = await fetchAPI(`chats/?${searchParams.toString()}`); + + if (!response.ok) { + throw new APIError( + 'Failed to get the conversations', + await errorCauses(response), + ); + } + + return response.json() as Promise; +}; + +export const KEY_LIST_CONVERSATION = 'conversations'; + +export function useConversations( + param: ConversationsParams, + queryConfig?: UseQueryOptions< + ConversationsResponse, + APIError, + ConversationsResponse + >, +) { + return useQuery({ + queryKey: [KEY_LIST_CONVERSATION, param], + queryFn: () => getConversations(param), + ...queryConfig, + }); +} + +export const useInfiniteConversations = (params: ConversationsParams) => { + return useAPIInfiniteQuery(KEY_LIST_CONVERSATION, getConversations, params); +}; diff --git a/src/frontend/apps/conversations/src/features/chat/api/useCreateConversation.tsx b/src/frontend/apps/conversations/src/features/chat/api/useCreateConversation.tsx new file mode 100644 index 0000000..306cee6 --- /dev/null +++ b/src/frontend/apps/conversations/src/features/chat/api/useCreateConversation.tsx @@ -0,0 +1,43 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { APIError, errorCauses, fetchAPI } from '@/api'; + +import { ChatConversation } from '../types'; + +import { KEY_LIST_CONVERSATION } from './useConversations'; + +interface ChatConversationParams { + title: string; +} + +export const createChatConversation = async ({ + title, +}: ChatConversationParams): Promise => { + const response = await fetchAPI(`chats/`, { + method: 'POST', + body: JSON.stringify({ + title, + }), + }); + + if (!response.ok) { + throw new APIError( + 'Failed to initiate a new chat conversation', + await errorCauses(response), + ); + } + + return response.json() as Promise; +}; + +export function useCreateChatConversation() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: createChatConversation, + onSuccess: () => { + void queryClient.invalidateQueries({ + queryKey: [KEY_LIST_CONVERSATION], + }); + }, + }); +} diff --git a/src/frontend/apps/conversations/src/features/chat/api/useRemoveConversation.tsx b/src/frontend/apps/conversations/src/features/chat/api/useRemoveConversation.tsx new file mode 100644 index 0000000..95fb016 --- /dev/null +++ b/src/frontend/apps/conversations/src/features/chat/api/useRemoveConversation.tsx @@ -0,0 +1,57 @@ +import { + UseMutationOptions, + useMutation, + useQueryClient, +} from '@tanstack/react-query'; + +import { APIError, errorCauses, fetchAPI } from '@/api'; + +import { KEY_LIST_CONVERSATION } from './useConversations'; + +interface RemoveConversationProps { + conversationId: string; +} + +export const removeConversation = async ({ + conversationId, +}: RemoveConversationProps): Promise => { + const response = await fetchAPI(`chats/${conversationId}/`, { + method: 'DELETE', + }); + + if (!response.ok) { + throw new APIError( + 'Failed to delete the conversation', + await errorCauses(response), + ); + } +}; + +type UseRemoveConversationOptions = UseMutationOptions< + void, + APIError, + RemoveConversationProps +>; + +export const useRemoveConversation = ( + options?: UseRemoveConversationOptions, +) => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: removeConversation, + ...options, + onSuccess: (data, variables, context) => { + void queryClient.invalidateQueries({ + queryKey: [KEY_LIST_CONVERSATION], + }); + if (options?.onSuccess) { + void options.onSuccess(data, variables, context); + } + }, + onError: (error, variables, context) => { + if (options?.onError) { + void options.onError(error, variables, context); + } + }, + }); +}; diff --git a/src/frontend/apps/conversations/src/features/chat/components/Chat.tsx b/src/frontend/apps/conversations/src/features/chat/components/Chat.tsx new file mode 100644 index 0000000..d75dc02 --- /dev/null +++ b/src/frontend/apps/conversations/src/features/chat/components/Chat.tsx @@ -0,0 +1,535 @@ +import { Message } from '@ai-sdk/ui-utils'; +import { Loader } from '@openfun/cunningham-react'; +import Image from 'next/image'; +import { useRouter } from 'next/router'; +import { useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import Markdown from 'react-markdown'; +import remarkGfm from 'remark-gfm'; + +import { Box, BoxButton, Icon, Text } from '@/components'; +import { DropdownMenu } from '@/components/DropdownMenu'; +import { useAuth } from '@/features/auth'; +import { useChat } from '@/features/chat/api/useChat'; +import { getConversation } from '@/features/chat/api/useConversation'; +import { useCreateChatConversation } from '@/features/chat/api/useCreateConversation'; + +import { usePendingChatStore } from '../stores/usePendingChatStore'; + +// Define Attachment type locally (mirroring backend structure) +export interface Attachment { + name?: string; + contentType?: string; + url: string; +} + +export const Chat = ({ + initialConversationId = undefined, +}: { + initialConversationId: string | undefined; +}) => { + const { t } = useTranslation(); + + const streamProtocol = 'data'; // or 'text' + const apiUrl = `chats/${initialConversationId}/conversation/?protocol=${streamProtocol}`; + + const router = useRouter(); + const { user } = useAuth(); + const [files, setFiles] = useState(null); + const fileInputRef = useRef(null); + const [isDragActive, setIsDragActive] = useState(false); + const [conversationId, setConversationId] = useState(initialConversationId); + const [initialConversationMessages, setInitialConversationMessages] = + useState(undefined); + const [pendingFirstMessage, setPendingFirstMessage] = useState<{ + event: React.FormEvent; + attachments?: FileList | null; + } | null>(null); + const [shouldAutoSubmit, setShouldAutoSubmit] = useState(false); + + const { mutate: createChatConversation } = useCreateChatConversation(); + + // Zustand store for pending chat state + const { + input: pendingInput, + files: pendingFiles, + setPendingChat, + clearPendingChat, + } = usePendingChatStore(); + + const { + messages, + input, + handleSubmit: baseHandleSubmit, + handleInputChange, + status, + } = useChat({ + id: conversationId, + initialMessages: initialConversationMessages, + api: apiUrl, + streamProtocol: streamProtocol, + sendExtraMessageFields: true, + }); + + // Synchronize conversationId state with prop when it changes (e.g., after navigation) + useEffect(() => { + setConversationId(initialConversationId); + }, [initialConversationId]); + + // On mount, if there is pending input/files, initialize state and set flag + useEffect(() => { + if ( + (pendingInput && pendingInput.trim()) || + (pendingFiles && pendingFiles.length > 0) + ) { + if (pendingInput) { + const syntheticEvent = { + target: { value: pendingInput }, + } as React.ChangeEvent; + handleInputChange(syntheticEvent); + } + if (pendingFiles) { + setFiles(pendingFiles); + } + setShouldAutoSubmit(true); + clearPendingChat(); + } else { + clearPendingChat(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // When shouldAutoSubmit is set, and input/files are ready, submit + useEffect(() => { + if (shouldAutoSubmit && (input.trim() || (files && files.length > 0))) { + // Create a synthetic event for form submission + const form = document.createElement('form'); + const syntheticFormEvent = { + preventDefault: () => {}, + target: form, + } as unknown as React.FormEvent; + handleSubmit(syntheticFormEvent); + setShouldAutoSubmit(false); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [shouldAutoSubmit, input, files]); + + // Fetch initial conversation messages if initialConversationId is provided and no pending input + useEffect(() => { + let ignore = false; + async function fetchInitialMessages() { + if (initialConversationId && !pendingInput) { + try { + const conversation = await getConversation({ + id: initialConversationId, + }); + if (!ignore) { + setInitialConversationMessages(conversation.messages); + } + } catch { + // Optionally handle error (e.g., setInitialConversationMessages([]) or show error) + if (!ignore) { + setInitialConversationMessages([]); + } + } + } + } + void fetchInitialMessages(); + return () => { + ignore = true; + }; + // Only run when initialConversationId or pendingInput changes + }, [initialConversationId, pendingInput]); + + // Custom handleSubmit to include attachments and handle chat creation + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + if (!conversationId) { + // Save the event and files, then create the chat + setPendingFirstMessage({ event, attachments: files }); + // Save input and files to Zustand store before navigation + setPendingChat(input, files); + void createChatConversation( + { title: input.length > 100 ? `${input.slice(0, 97)}...` : input }, + { + onSuccess: (data) => { + setConversationId(data.id); + // Update the URL to /chat/[id]/ + void router.push(`/chat/${data.id}/`); + // After setting the conversationId, submit the pending message + setTimeout(() => { + if (pendingFirstMessage) { + if ( + pendingFirstMessage.attachments && + pendingFirstMessage.attachments.length > 0 + ) { + baseHandleSubmit(pendingFirstMessage.event, { + experimental_attachments: pendingFirstMessage.attachments, + }); + } else { + baseHandleSubmit(pendingFirstMessage.event); + } + setFiles(null); + if (fileInputRef.current) { + fileInputRef.current.value = ''; + } + setPendingFirstMessage(null); + } + }, 0); + }, + }, + ); + return; + } + if (files && files.length > 0) { + baseHandleSubmit(event, { experimental_attachments: files }); + } else { + baseHandleSubmit(event); + } + setFiles(null); + if (fileInputRef.current) { + fileInputRef.current.value = ''; + } + }; + + return ( + + + {messages.map((message) => ( + + + {`${message.role === 'user' ? user?.full_name : message.role}:`} + + + {message.content && ( + , + }} + > + {message.content} + + )} + + {message.toolInvocations?.map((toolInvocation) => ( + + {`${toolInvocation.toolName}(${JSON.stringify(toolInvocation.args, null, 2)})`} + + ))} + {/* Show attachments if present */} + {message.experimental_attachments?.map( + (attachment: Attachment, index: number) => + attachment.contentType?.includes('image/') ? ( + {attachment.name + ) : attachment.contentType?.includes('text/') ? ( +
+ {attachment.url} +
+ ) : null, + )} +
+
+
+ ))} + {(status === 'streaming' || status === 'submitted') && ( + + + {t('Generating...')} + + )} +
+ + {messages.length === 0 && ( + + + {t('Welcome to your assistant!')} + + + {t('Start a conversation by typing a message below.')} + + + )} + + {/* File preview */} + {files && files.length > 0 && ( + + {Array.from(files).map((file, idx) => { + const { type, name } = file; + const removeFile = () => { + if (!files) { + return; + } + const dt = new DataTransfer(); + Array.from(files).forEach((f, i) => { + if (i !== idx) { + dt.items.add(f); + } + }); + setFiles(dt.files.length > 0 ? dt.files : null); + }; + if (type.startsWith('image/')) { + return ( + + {name} + + {name} + + + + + + ); + } else if (type.startsWith('text/')) { + return ( + + + + {name} + + + + + + ); + } + return null; + })} + + )} + +
{ + e.preventDefault(); + setIsDragActive(true); + }} + onDragLeave={(e) => { + e.preventDefault(); + setIsDragActive(false); + }} + onDrop={(e) => { + e.preventDefault(); + setIsDragActive(false); + if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { + setFiles((prev) => { + if (!prev || prev.length === 0) { + return e.dataTransfer.files; + } + const dt = new DataTransfer(); + Array.from(prev).forEach((f) => dt.items.add(f)); + Array.from(e.dataTransfer.files).forEach((f) => { + if ( + !Array.from(prev).some( + (pf) => + pf.name === f.name && + pf.size === f.size && + pf.lastModified === f.lastModified, + ) + ) { + dt.items.add(f); + } + }); + return dt.files; + }); + } + }} + > + + + <svg/onload='+/\"/+/onmouseover=1/+/[*/[]/+alert(1)//'>", + ]; + + // Directory Traversal + const traversalUrls = [ + 'https://example.com/../../etc/passwd', + 'https://example.com/..%2F..%2Fetc%2Fpasswd', + 'https://example.com/..\\..\\Windows\\System32\\config\\SAM', + ]; + + // SQL Injection + const sqlInjectionUrls = [ + "https://example.com/' OR '1'='1", + 'https://example.com/; DROP TABLE users;', + "https://example.com/' OR 1=1 --", + ]; + + // Malicious Encodings + const encodingUrls = [ + "https://example.com/%3Cscript%3Ealert('xss')%3C/script%3E", + 'https://example.com/%00', + 'https://example.com/\\0', + 'https://example.com/file.php%00.jpg', + ]; + + // Unauthorized Protocols + const protocolUrls = [ + 'file:///etc/passwd', + 'ftp://attacker.com/malware.exe', + 'telnet://attacker.com', + ]; + + // Long URLs + const longUrls = ['https://example.com/' + 'a'.repeat(2001)]; + + // Safe URLs + const safeUrls = [ + 'https://example.com', + 'https://example.com/path/to/file', + 'https://example.com?param=value', + 'https://example.com#section', + ]; + + describe('should block XSS attacks', () => { + xssUrls.forEach((url) => { + it(`should block ${url}`, () => { + expect(isSafeUrl(url)).toBe(false); + }); + }); + }); + + describe('should block directory traversal', () => { + traversalUrls.forEach((url) => { + it(`should block ${url}`, () => { + expect(isSafeUrl(url)).toBe(false); + }); + }); + }); + + describe('should block SQL injection', () => { + sqlInjectionUrls.forEach((url) => { + it(`should block ${url}`, () => { + expect(isSafeUrl(url)).toBe(false); + }); + }); + }); + + describe('should block malicious encodings', () => { + encodingUrls.forEach((url) => { + it(`should block ${url}`, () => { + expect(isSafeUrl(url)).toBe(false); + }); + }); + }); + + describe('should block unauthorized protocols', () => { + protocolUrls.forEach((url) => { + it(`should block ${url}`, () => { + expect(isSafeUrl(url)).toBe(false); + }); + }); + }); + + describe('should block long URLs', () => { + longUrls.forEach((url) => { + it(`should block ${url}`, () => { + expect(isSafeUrl(url)).toBe(false); + }); + }); + }); + + describe('should allow safe URLs', () => { + safeUrls.forEach((url) => { + it(`should allow ${url}`, () => { + expect(isSafeUrl(url)).toBe(true); + }); + }); + }); +}); diff --git a/src/frontend/apps/conversations/src/utils/children.ts b/src/frontend/apps/conversations/src/utils/children.ts new file mode 100644 index 0000000..048c76c --- /dev/null +++ b/src/frontend/apps/conversations/src/utils/children.ts @@ -0,0 +1,9 @@ +import { Children, ReactNode } from 'react'; + +export const hasChildrens = (element: ReactNode): boolean => { + let hasChildren = false; + Children.forEach(element, (child: ReactNode) => { + hasChildren = hasChildren || !!child; + }); + return hasChildren; +}; diff --git a/src/frontend/apps/conversations/src/utils/index.ts b/src/frontend/apps/conversations/src/utils/index.ts new file mode 100644 index 0000000..4f82373 --- /dev/null +++ b/src/frontend/apps/conversations/src/utils/index.ts @@ -0,0 +1,4 @@ +export * from './userAgent'; +export * from './string'; +export * from './styleBuilder'; +export * from './system'; diff --git a/src/frontend/apps/conversations/src/utils/storages.ts b/src/frontend/apps/conversations/src/utils/storages.ts new file mode 100644 index 0000000..b7f152c --- /dev/null +++ b/src/frontend/apps/conversations/src/utils/storages.ts @@ -0,0 +1,52 @@ +/** + * @fileOverview This module provides utilities to interact with local storage safely. + */ + +interface SyncStorage { + getItem(key: string): string | null; + setItem(key: string, value: string): void; + removeItem(key: string): void; +} + +/** + * @namespace safeLocalStorage + * @description A utility for safely interacting with localStorage. + * It checks if the `window` object is defined before attempting to access localStorage, + * preventing errors in environments where `window` is not available. + */ +export const safeLocalStorage: SyncStorage = { + /** + * Retrieves an item from localStorage. + * @param {string} key - The key of the item to retrieve. + * @returns {string | null} The item's value, or null if the item does not exist or if localStorage is not available. + */ + getItem: (key: string): string | null => { + if (typeof window === 'undefined') { + return null; + } + return localStorage.getItem(key); + }, + /** + * Sets an item in localStorage. + * @param {string} key - The key of the item to set. + * @param {string} value - The value to set for the item. + * @returns {void} + */ + setItem: (key: string, value: string): void => { + if (typeof window === 'undefined') { + return; + } + localStorage.setItem(key, value); + }, + /** + * Removes an item from localStorage. + * @param {string} key - The key of the item to remove. + * @returns {void} + */ + removeItem: (key: string): void => { + if (typeof window === 'undefined') { + return; + } + localStorage.removeItem(key); + }, +}; diff --git a/src/frontend/apps/conversations/src/utils/string.ts b/src/frontend/apps/conversations/src/utils/string.ts new file mode 100644 index 0000000..04a7e4f --- /dev/null +++ b/src/frontend/apps/conversations/src/utils/string.ts @@ -0,0 +1,5 @@ +export const isValidEmail = (email: string) => { + return !!email.match( + /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z\-0-9]{2,}))$/, + ); +}; diff --git a/src/frontend/apps/conversations/src/utils/styleBuilder.ts b/src/frontend/apps/conversations/src/utils/styleBuilder.ts new file mode 100644 index 0000000..f45f3cb --- /dev/null +++ b/src/frontend/apps/conversations/src/utils/styleBuilder.ts @@ -0,0 +1,78 @@ +import { tokens } from '@/cunningham/cunningham-tokens'; + +/* eslint-disable @typescript-eslint/no-unused-vars */ +const { + '0': _0, + st, + t, + s, + b, + bx, + l, + ...spacingsLight +} = tokens.themes.default.theme.spacings; +/* eslint-enable @typescript-eslint/no-unused-vars */ + +const spacings = { + xtiny: tokens.themes.default.theme.spacings['st'], + tiny: tokens.themes.default.theme.spacings['t'], + small: tokens.themes.default.theme.spacings['s'], + big: tokens.themes.default.theme.spacings['b'], + xbig: tokens.themes.default.theme.spacings['bx'], + large: tokens.themes.default.theme.spacings['l'], + ...spacingsLight, +}; + +type SpacingsKey = keyof typeof spacings; +export type Spacings = SpacingsKey | (string & {}); + +export const spacingValue = (value?: Spacings) => + value && value in spacings ? spacings[value as SpacingsKey] : value; + +export type MarginPadding = + | Spacings + | { + vertical?: Spacings; + horizontal?: Spacings; + top?: Spacings; + bottom?: Spacings; + left?: Spacings; + right?: Spacings; + all?: Spacings; + }; + +export const stylesPadding = (pad: MarginPadding) => { + if (typeof pad === 'object') { + return { + 'padding-top': spacingValue(pad.top || pad.vertical || pad.all), + 'padding-bottom': spacingValue(pad.bottom || pad.vertical || pad.all), + 'padding-left': spacingValue(pad.left || pad.horizontal || pad.all), + 'padding-right': spacingValue(pad.right || pad.horizontal || pad.all), + }; + } else { + return { + padding: spacingValue(pad), + }; + } +}; + +export const stylesMargin = (margin: MarginPadding) => { + if (typeof margin === 'object') { + return { + 'margin-top': spacingValue(margin.top || margin.vertical || margin.all), + 'margin-bottom': spacingValue( + margin.bottom || margin.vertical || margin.all, + ), + 'margin-left': spacingValue( + margin.left || margin.horizontal || margin.all, + ), + 'margin-right': spacingValue( + margin.right || margin.horizontal || margin.all, + ), + }; + } else { + return { + margin: spacingValue(margin), + }; + } +}; diff --git a/src/frontend/apps/conversations/src/utils/system.ts b/src/frontend/apps/conversations/src/utils/system.ts new file mode 100644 index 0000000..a8c24f3 --- /dev/null +++ b/src/frontend/apps/conversations/src/utils/system.ts @@ -0,0 +1,2 @@ +export const sleep = (ms: number) => + new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/src/frontend/apps/conversations/src/utils/url.ts b/src/frontend/apps/conversations/src/utils/url.ts new file mode 100644 index 0000000..4a8c4b6 --- /dev/null +++ b/src/frontend/apps/conversations/src/utils/url.ts @@ -0,0 +1,47 @@ +export function isSafeUrl(url: string): boolean { + try { + // Parse the URL with a base to support relative URLs + const parsed = new URL(url, window.location.origin); + + // List of allowed protocols + const allowedProtocols = ['http:', 'https:']; + + // Check protocol + if (!allowedProtocols.includes(parsed.protocol)) { + return false; + } + + // Check for dangerous characters in the pathname + const dangerousChars = ['<', '>', '"', "'", '(', ')', ';', '=', '{', '}']; + if (dangerousChars.some((char) => parsed.pathname.includes(char))) { + return false; + } + + // Check URL length (protection against buffer overflow attacks) + if (url.length > 2000) { + return false; + } + + // Check for malicious encodings + if (url.includes('%00') || url.includes('\\0')) { + return false; + } + + // Check for XSS injection attempts + const xssPatterns = [ + '<script', + 'javascript:', + 'data:', + 'vbscript:', + 'expression(', + ]; + if (xssPatterns.some((pattern) => url.toLowerCase().includes(pattern))) { + return false; + } + + // Check for directory traversal attempts + return !(url.includes('..') || url.includes('../') || url.includes('..\\')); + } catch { + return false; + } +} diff --git a/src/frontend/apps/conversations/src/utils/userAgent.ts b/src/frontend/apps/conversations/src/utils/userAgent.ts new file mode 100644 index 0000000..fa30acf --- /dev/null +++ b/src/frontend/apps/conversations/src/utils/userAgent.ts @@ -0,0 +1,2 @@ +export const isFirefox = () => + navigator.userAgent.toLowerCase().indexOf('firefox') > -1; diff --git a/src/frontend/apps/conversations/stylelint.config.js b/src/frontend/apps/conversations/stylelint.config.js new file mode 100644 index 0000000..7c862f0 --- /dev/null +++ b/src/frontend/apps/conversations/stylelint.config.js @@ -0,0 +1,9 @@ +module.exports = { + extends: ['stylelint-config-standard', 'stylelint-prettier/recommended'], + rules: { + 'custom-property-pattern': null, + 'selector-class-pattern': null, + 'no-descending-specificity': null, + }, + ignoreFiles: ['out/**/*'], +}; diff --git a/src/frontend/apps/conversations/tsconfig.json b/src/frontend/apps/conversations/tsconfig.json new file mode 100644 index 0000000..8866055 --- /dev/null +++ b/src/frontend/apps/conversations/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"], + "@/docs/*": ["./src/features/docs/*"] + } + }, + "include": [ + "src/custom-next.d.ts", + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts" + ], + "exclude": ["node_modules"] +} diff --git a/src/frontend/apps/e2e/.eslintrc.js b/src/frontend/apps/e2e/.eslintrc.js new file mode 100644 index 0000000..dd78b24 --- /dev/null +++ b/src/frontend/apps/e2e/.eslintrc.js @@ -0,0 +1,9 @@ +module.exports = { + root: true, + extends: ['conversations/playwright'], + parserOptions: { + tsconfigRootDir: __dirname, + project: ['./tsconfig.json'], + }, + ignorePatterns: ['node_modules'], +}; diff --git a/src/frontend/apps/e2e/.gitignore b/src/frontend/apps/e2e/.gitignore new file mode 100644 index 0000000..d248afc --- /dev/null +++ b/src/frontend/apps/e2e/.gitignore @@ -0,0 +1,7 @@ +# e2e +test-results/ +report/ +blob-report/ +playwright/.auth/ +playwright/.cache/ +screenshots/ diff --git a/src/frontend/apps/e2e/__tests__/app-conversations/404.spec.ts b/src/frontend/apps/e2e/__tests__/app-conversations/404.spec.ts new file mode 100644 index 0000000..078a7c5 --- /dev/null +++ b/src/frontend/apps/e2e/__tests__/app-conversations/404.spec.ts @@ -0,0 +1,27 @@ +import { expect, test } from '@playwright/test'; + +test.beforeEach(async ({ page }) => { + await page.goto('/'); + await expect( + page.locator('header').first().locator('h2').getByText('Conversations'), + ).toBeVisible(); + await page.goto('unknown-page404'); +}); + +test.describe('404', () => { + test('Checks all the elements are visible', async ({ page }) => { + await expect( + page.getByText( + 'It seems that the page you are looking for does not exist or cannot be displayed correctly.', + ), + ).toBeVisible(); + await expect(page.getByText('Home')).toBeVisible(); + }); + + test('checks go back to home page redirects to home page', async ({ + page, + }) => { + await page.getByText('Home').click(); + await expect(page).toHaveURL('/'); + }); +}); diff --git a/src/frontend/apps/e2e/__tests__/app-conversations/assets/logo-suite-numerique.png b/src/frontend/apps/e2e/__tests__/app-conversations/assets/logo-suite-numerique.png new file mode 100644 index 0000000..243c966 Binary files /dev/null and b/src/frontend/apps/e2e/__tests__/app-conversations/assets/logo-suite-numerique.png differ diff --git a/src/frontend/apps/e2e/__tests__/app-conversations/assets/test.html b/src/frontend/apps/e2e/__tests__/app-conversations/assets/test.html new file mode 100644 index 0000000..a1247b9 --- /dev/null +++ b/src/frontend/apps/e2e/__tests__/app-conversations/assets/test.html @@ -0,0 +1,22 @@ +<html> + <head> + <title>Test unsafe file</title> + </head> + <body> + <h1>Hello svg</h1> + <img src="test.jpg" alt="test" /> + <svg + xmlns="http://www.w3.org/2000/svg" + width="100" + height="100" + viewBox="0 0 100 100" + > + <circle cx="50" cy="30" r="20" fill="#3498db" /> + <polygon + points="50,10 55,20 65,20 58,30 60,40 50,35 40,40 42,30 35,20 45,20" + fill="#f1c40f" + /> + <text x="50" y="70" text-anchor="middle" fill="white">Hello svg</text> + </svg> + </body> +</html> diff --git a/src/frontend/apps/e2e/__tests__/app-conversations/assets/test.svg b/src/frontend/apps/e2e/__tests__/app-conversations/assets/test.svg new file mode 100644 index 0000000..6980c93 --- /dev/null +++ b/src/frontend/apps/e2e/__tests__/app-conversations/assets/test.svg @@ -0,0 +1,13 @@ +<svg + width="100" + height="100" + viewBox="0 0 100 100" + xmlns="http://www.w3.org/2000/svg" +> + <circle cx="50" cy="30" r="20" fill="#3498db" /> + <polygon + points="50,10 55,20 65,20 58,30 60,40 50,35 40,40 42,30 35,20 45,20" + fill="#f1c40f" + /> + <text x="50" y="70" text-anchor="middle" fill="white">Hello svg</text> +</svg> diff --git a/src/frontend/apps/e2e/__tests__/app-conversations/auth.setup.ts b/src/frontend/apps/e2e/__tests__/app-conversations/auth.setup.ts new file mode 100644 index 0000000..a6e1898 --- /dev/null +++ b/src/frontend/apps/e2e/__tests__/app-conversations/auth.setup.ts @@ -0,0 +1,59 @@ +import { FullConfig, FullProject, chromium, expect } from '@playwright/test'; + +import { keyCloakSignIn } from './common'; + +const saveStorageState = async ( + browserConfig: FullProject<unknown, unknown>, +) => { + const browserName = browserConfig?.name || 'chromium'; + + const { storageState, ...useConfig } = browserConfig?.use; + const browser = await chromium.launch(); + const context = await browser.newContext(useConfig); + const page = await context.newPage(); + + try { + await page.goto('/', { waitUntil: 'networkidle' }); + await page.content(); + await expect(page.getByText('Docs').first()).toBeVisible(); + + await keyCloakSignIn(page, browserName); + + await expect( + page.locator('header').first().getByRole('button', { + name: 'Logout', + }), + ).toBeVisible(); + + await page.context().storageState({ + path: storageState as string, + }); + } catch (error) { + console.log(error); + + await page.screenshot({ + path: `./screenshots/${browserName}-${Date.now()}.png`, + }); + // Get console logs + const consoleLogs = await page.evaluate(() => + console.log(window.console.log), + ); + console.log(consoleLogs); + } finally { + await browser.close(); + } +}; + +async function globalSetup(config: FullConfig) { + /* eslint-disable @typescript-eslint/no-non-null-assertion */ + const chromeConfig = config.projects.find((p) => p.name === 'chromium')!; + const firefoxConfig = config.projects.find((p) => p.name === 'firefox')!; + const webkitConfig = config.projects.find((p) => p.name === 'webkit')!; + /* eslint-enable @typescript-eslint/no-non-null-assertion */ + + await saveStorageState(chromeConfig); + await saveStorageState(webkitConfig); + await saveStorageState(firefoxConfig); +} + +export default globalSetup; diff --git a/src/frontend/apps/e2e/__tests__/app-conversations/common.ts b/src/frontend/apps/e2e/__tests__/app-conversations/common.ts new file mode 100644 index 0000000..db7e225 --- /dev/null +++ b/src/frontend/apps/e2e/__tests__/app-conversations/common.ts @@ -0,0 +1,328 @@ +import { Page, expect } from '@playwright/test'; + +export const CONFIG = { + CRISP_WEBSITE_ID: null, + ENVIRONMENT: 'development', + FRONTEND_CSS_URL: null, + FRONTEND_HOMEPAGE_FEATURE_ENABLED: true, + FRONTEND_THEME: null, + MEDIA_BASE_URL: 'http://localhost:8083', + LANGUAGES: [ + ['en-us', 'English'], + ['fr-fr', 'Franรงais'], + ['de-de', 'Deutsch'], + ['nl-nl', 'Nederlands'], + ['es-es', 'Espaรฑol'], + ], + LANGUAGE_CODE: 'en-us', + POSTHOG_KEY: {}, + SENTRY_DSN: null, + theme_customization: {}, +} as const; + +export const overrideConfig = async ( + page: Page, + newConfig: { [K in keyof typeof CONFIG]?: unknown }, +) => + await page.route('**/api/v1.0/config/', async (route) => { + const request = route.request(); + if (request.method().includes('GET')) { + await route.fulfill({ + json: { + ...CONFIG, + ...newConfig, + }, + }); + } else { + await route.continue(); + } + }); + +export const keyCloakSignIn = async ( + page: Page, + browserName: string, + fromHome: boolean = true, +) => { + if (fromHome) { + await page.getByRole('button', { name: 'Start Writing' }).first().click(); + } + + const login = `user-e2e-${browserName}`; + const password = `password-e2e-${browserName}`; + + await expect( + page.locator('.login-pf-page-header').getByText('conversations'), + ).toBeVisible(); + + if (await page.getByLabel('Restart login').isVisible()) { + await page.getByLabel('Restart login').click(); + } + + await page.getByRole('textbox', { name: 'username' }).fill(login); + await page.getByRole('textbox', { name: 'password' }).fill(password); + await page.click('input[type="submit"]', { force: true }); +}; + +export const randomName = (name: string, browserName: string, length: number) => + Array.from({ length }, (_el, index) => { + return `${browserName}-${Math.floor(Math.random() * 10000)}-${index}-${name}`; + }); + +export const createDoc = async ( + page: Page, + docName: string, + browserName: string, + length: number = 1, +) => { + const randomDocs = randomName(docName, browserName, length); + + for (let i = 0; i < randomDocs.length; i++) { + const header = page.locator('header').first(); + await header.locator('h2').getByText('Docs').click(); + + await page + .getByRole('button', { + name: 'New doc', + }) + .click(); + + await page.waitForURL('**/docs/**', { + timeout: 10000, + waitUntil: 'networkidle', + }); + + const input = page.getByLabel('doc title input'); + await expect(input).toBeVisible(); + await expect(input).toHaveText(''); + await input.click(); + + await input.fill(randomDocs[i]); + await input.blur(); + } + + return randomDocs; +}; + +export const verifyDocName = async (page: Page, docName: string) => { + await expect( + page.getByLabel('It is the card information about the document.'), + ).toBeVisible({ + timeout: 10000, + }); + + try { + await expect( + page.getByRole('textbox', { name: 'doc title input' }), + ).toHaveText(docName); + } catch { + await expect(page.getByRole('heading', { name: docName })).toBeVisible(); + } +}; + +export const addNewMember = async ( + page: Page, + index: number, + role: 'Administrator' | 'Owner' | 'Editor' | 'Reader', + fillText: string = 'user ', +) => { + const responsePromiseSearchUser = page.waitForResponse( + (response) => + response.url().includes(`/users/?q=${encodeURIComponent(fillText)}`) && + response.status() === 200, + ); + + const inputSearch = page.getByRole('combobox', { + name: 'Quick search input', + }); + + // Select a new user + await inputSearch.fill(fillText); + + // Intercept response + const responseSearchUser = await responsePromiseSearchUser; + const users = (await responseSearchUser.json()) as { + email: string; + }[]; + + // Choose user + await page.getByRole('option', { name: users[index].email }).click(); + + // Choose a role + await page.getByLabel('doc-role-dropdown').click(); + await page.getByLabel(role).click(); + await page.getByRole('button', { name: 'Invite' }).click(); + + return users[index].email; +}; + +export const getGridRow = async (page: Page, title: string) => { + const docsGrid = page.getByRole('grid'); + await expect(docsGrid).toBeVisible(); + await expect(page.getByTestId('grid-loader')).toBeHidden(); + + const rows = docsGrid.getByRole('row'); + + const row = rows.filter({ + hasText: title, + }); + + await expect(row).toBeVisible(); + + return row; +}; + +interface GoToGridDocOptions { + nthRow?: number; + title?: string; +} +export const goToGridDoc = async ( + page: Page, + { nthRow = 1, title }: GoToGridDocOptions = {}, +) => { + const header = page.locator('header').first(); + await header.locator('h2').getByText('Docs').click(); + + const docsGrid = page.getByTestId('docs-grid'); + await expect(docsGrid).toBeVisible(); + await expect(page.getByTestId('grid-loader')).toBeHidden(); + + const rows = docsGrid.getByRole('row'); + + const row = title + ? rows.filter({ + hasText: title, + }) + : rows.nth(nthRow); + + await expect(row).toBeVisible(); + + const docTitleContent = row.locator('[aria-describedby="doc-title"]').first(); + const docTitle = await docTitleContent.textContent(); + expect(docTitle).toBeDefined(); + + await row.getByRole('link').first().click(); + + return docTitle as string; +}; + +export const mockedDocument = async (page: Page, json: object) => { + await page.route('**/documents/**/', async (route) => { + const request = route.request(); + if ( + request.method().includes('GET') && + !request.url().includes('page=') && + !request.url().includes('versions') && + !request.url().includes('accesses') && + !request.url().includes('invitations') + ) { + await route.fulfill({ + json: { + id: 'mocked-document-id', + content: '', + title: 'Mocked document', + accesses: [], + abilities: { + destroy: false, // Means not owner + link_configuration: false, + versions_destroy: false, + versions_list: true, + versions_retrieve: true, + accesses_manage: false, // Means not admin + update: false, + partial_update: false, // Means not editor + retrieve: true, + }, + link_reach: 'restricted', + created_at: '2021-09-01T09:00:00Z', + ...json, + }, + }); + } else { + await route.continue(); + } + }); +}; + +export const mockedInvitations = async (page: Page, json?: object) => { + await page.route('**/invitations/**/', async (route) => { + const request = route.request(); + if ( + request.method().includes('GET') && + request.url().includes('invitations') && + request.url().includes('page=') + ) { + await route.fulfill({ + json: { + count: 1, + next: null, + previous: null, + results: [ + { + id: '120ec765-43af-4602-83eb-7f4e1224548a', + abilities: { + destroy: true, + update: true, + partial_update: true, + retrieve: true, + }, + created_at: '2024-10-03T12:19:26.107687Z', + email: 'test@invitation.test', + document: '4888c328-8406-4412-9b0b-c0ba5b9e5fb6', + role: 'editor', + issuer: '7380f42f-02eb-4ad5-b8f0-037a0e66066d', + is_expired: false, + ...json, + }, + ], + }, + }); + } else { + await route.continue(); + } + }); +}; + +export const mockedAccesses = async (page: Page, json?: object) => { + await page.route('**/accesses/**/', async (route) => { + const request = route.request(); + if ( + request.method().includes('GET') && + request.url().includes('accesses') && + request.url().includes('page=') + ) { + await route.fulfill({ + json: { + count: 1, + next: null, + previous: null, + results: [ + { + id: 'bc8bbbc5-a635-4f65-9817-fd1e9ec8ef87', + user: { + id: 'b4a21bb3-722e-426c-9f78-9d190eda641c', + email: 'test@accesses.test', + }, + team: '', + role: 'reader', + abilities: { + destroy: true, + update: true, + partial_update: true, + retrieve: true, + set_role_to: ['administrator', 'editor'], + }, + ...json, + }, + ], + }, + }); + } else { + await route.continue(); + } + }); +}; + +export const expectLoginPage = async (page: Page) => + await expect( + page.getByRole('heading', { name: 'Collaborative writing' }), + ).toBeVisible(); diff --git a/src/frontend/apps/e2e/__tests__/app-conversations/config.spec.ts b/src/frontend/apps/e2e/__tests__/app-conversations/config.spec.ts new file mode 100644 index 0000000..4377d9e --- /dev/null +++ b/src/frontend/apps/e2e/__tests__/app-conversations/config.spec.ts @@ -0,0 +1,140 @@ +import path from 'path'; + +import { expect, test } from '@playwright/test'; + +import { CONFIG, createDoc, overrideConfig } from './common'; + +test.describe('Config', () => { + test('it checks that sentry is trying to init from config endpoint', async ({ + page, + }) => { + await overrideConfig(page, { + SENTRY_DSN: 'https://sentry.io/123', + }); + + const invalidMsg = 'Invalid Sentry Dsn: https://sentry.io/123'; + const consoleMessage = page.waitForEvent('console', { + timeout: 5000, + predicate: (msg) => msg.text().includes(invalidMsg), + }); + + await page.goto('/'); + + expect((await consoleMessage).text()).toContain(invalidMsg); + }); + + test('it checks that media server is configured from config endpoint', async ({ + page, + browserName, + }) => { + await page.goto('/'); + + await createDoc(page, 'doc-media', browserName, 1); + + const fileChooserPromise = page.waitForEvent('filechooser'); + + await page.locator('.bn-block-outer').last().fill('Anything'); + await page.locator('.bn-block-outer').last().fill('/'); + await page.getByText('Resizable image with caption').click(); + await page.getByText('Upload image').click(); + + const fileChooser = await fileChooserPromise; + await fileChooser.setFiles( + path.join(__dirname, 'assets/logo-suite-numerique.png'), + ); + + const image = page.getByRole('img', { name: 'logo-suite-numerique.png' }); + + await expect(image).toBeVisible(); + + // Check src of image + expect(await image.getAttribute('src')).toMatch( + /http:\/\/localhost:8083\/media\/.*\/attachments\/.*.png/, + ); + }); + + test('it checks that Crisp is trying to init from config endpoint', async ({ + page, + }) => { + await overrideConfig(page, { + CRISP_WEBSITE_ID: '1234', + }); + + await page.goto('/'); + + await expect( + page.locator('#crisp-chatbox').getByText('Invalid website'), + ).toBeVisible(); + }); + + test('it checks FRONTEND_CSS_URL config', async ({ page }) => { + await overrideConfig(page, { + FRONTEND_CSS_URL: 'http://localhost:123465/css/style.css', + }); + + await page.goto('/'); + + await expect( + page + .locator('head link[href="http://localhost:123465/css/style.css"]') + .first(), + ).toBeAttached(); + }); + + test('it checks theme_customization.translations config', async ({ + page, + }) => { + await overrideConfig(page, { + theme_customization: { + translations: { + en: { + translation: { + Docs: 'MyCustomDocs', + }, + }, + }, + }, + }); + + await page.goto('/'); + + await expect(page.getByText('MyCustomDocs')).toBeAttached(); + }); +}); + +test.describe('Config: Not logged', () => { + test.use({ storageState: { cookies: [], origins: [] } }); + + test('it checks the config api is called', async ({ page }) => { + const responsePromise = page.waitForResponse( + (response) => + response.url().includes('/config/') && response.status() === 200, + ); + + await page.goto('/'); + + const response = await responsePromise; + expect(response.ok()).toBeTruthy(); + + const json = (await response.json()) as typeof CONFIG; + const { theme_customization, ...configApi } = json; + expect(theme_customization).toBeDefined(); + const { theme_customization: _, ...CONFIG_LEFT } = CONFIG; + + expect(configApi).toStrictEqual(CONFIG_LEFT); + }); + + test('it checks that theme is configured from config endpoint', async ({ + page, + }) => { + await overrideConfig(page, { + FRONTEND_THEME: 'dsfr', + }); + + await page.goto('/'); + + const header = page.locator('header').first(); + // alt 'Gouvernement Logo' comes from the theme + await expect(header.getByAltText('Gouvernement Logo')).toBeVisible(); + }); +}); diff --git a/src/frontend/apps/e2e/__tests__/app-conversations/footer.spec.ts b/src/frontend/apps/e2e/__tests__/app-conversations/footer.spec.ts new file mode 100644 index 0000000..e372be0 --- /dev/null +++ b/src/frontend/apps/e2e/__tests__/app-conversations/footer.spec.ts @@ -0,0 +1,147 @@ +import { expect, test } from '@playwright/test'; + +import { overrideConfig } from './common'; + +test.describe('Footer', () => { + test.use({ storageState: { cookies: [], origins: [] } }); + + test('checks the footer is not displayed if no config', async ({ page }) => { + await overrideConfig(page, { + theme_customization: {}, + }); + + await page.goto('/'); + await expect(page.locator('footer')).toBeHidden(); + }); + + test('checks all the elements are visible', async ({ page }) => { + await page.goto('/'); + const footer = page.locator('footer').first(); + + await expect(footer.getByAltText('Docs Logo')).toBeVisible(); + await expect(footer.getByRole('heading', { name: 'Docs' })).toBeVisible(); + + await expect(footer.getByRole('link', { name: 'Github' })).toBeVisible(); + await expect(footer.getByRole('link', { name: 'DINUM' })).toBeVisible(); + + await expect( + footer.getByRole('link', { name: 'BlockNote.js' }), + ).toBeVisible(); + await expect( + footer.getByRole('link', { name: 'Legal Notice' }), + ).toBeVisible(); + await expect( + footer.getByRole('link', { name: 'Personal data and cookies' }), + ).toBeVisible(); + await expect( + footer.getByRole('link', { name: 'Accessibility' }), + ).toBeVisible(); + + await expect( + footer.getByText( + 'Unless otherwise stated, all content on this site is under licence', + ), + ).toBeVisible(); + + // Check the translation + const header = page.locator('header').first(); + await header.getByRole('button').getByText('English').click(); + await page.getByLabel('Franรงais').click(); + + await expect( + page.locator('footer').getByText('Mentions lรฉgales'), + ).toBeVisible(); + }); + + test('checks the footer is correctly overrided', async ({ page }) => { + await overrideConfig(page, { + FRONTEND_THEME: 'dsfr', + theme_customization: { + footer: { + default: { + logo: { + src: '/assets/logo-gouv.svg', + width: '220px', + alt: 'Gouvernement Logo', + }, + externalLinks: [ + { + label: 'legifrance.gouv.fr', + href: '#', + }, + { + label: 'info.gouv.fr', + href: '#', + }, + ], + legalLinks: [ + { + label: 'Legal link', + href: '#', + }, + ], + bottomInformation: { + label: 'Some bottom information text', + link: { + label: 'a custom label', + href: '#', + }, + }, + }, + fr: { + bottomInformation: { + label: "Text d'information en bas de page en franรงais", + link: { + label: 'un label personnalisรฉ', + href: '#', + }, + }, + }, + }, + }, + }); + + await page.goto('/'); + const footer = page.locator('footer').first(); + + await expect(footer.getByAltText('Gouvernement Logo')).toBeVisible(); + + await expect(footer.getByRole('heading', { name: 'Docs' })).toBeHidden(); + await expect(footer.getByText('BETA')).toBeHidden(); + + await expect( + footer.getByRole('link', { name: 'legifrance.gouv.fr' }), + ).toBeVisible(); + + await expect( + footer.getByRole('link', { name: 'info.gouv.fr' }), + ).toBeVisible(); + + await expect( + footer.getByRole('link', { name: 'Legal link' }), + ).toBeVisible(); + + await expect( + footer.getByText('Some bottom information text'), + ).toBeVisible(); + + await expect( + footer.getByRole('link', { name: 'a custom label' }), + ).toBeVisible(); + + // Check the translation + const header = page.locator('header').first(); + await header.getByRole('button').getByText('English').click(); + await page.getByLabel('Franรงais').click(); + + await expect( + page + .locator('footer') + .getByText("Text d'information en bas de page en franรงais"), + ).toBeVisible(); + + await expect( + footer.getByRole('link', { name: 'un label personnalisรฉ' }), + ).toBeVisible(); + }); +}); diff --git a/src/frontend/apps/e2e/__tests__/app-conversations/header.spec.ts b/src/frontend/apps/e2e/__tests__/app-conversations/header.spec.ts new file mode 100644 index 0000000..1abca24 --- /dev/null +++ b/src/frontend/apps/e2e/__tests__/app-conversations/header.spec.ts @@ -0,0 +1,130 @@ +import { expect, test } from '@playwright/test'; + +import { expectLoginPage, keyCloakSignIn, overrideConfig } from './common'; + +test.describe('Header', () => { + test('checks all the elements are visible', async ({ page }) => { + await page.goto('/'); + + const header = page.locator('header').first(); + + await expect(header.getByLabel('Docs Logo')).toBeVisible(); + await expect(header.locator('h2').getByText('Docs')).toHaveCSS( + 'font-family', + /Roboto/i, + ); + + await expect( + header.getByRole('button', { + name: 'Logout', + }), + ).toBeVisible(); + + await expect(header.getByText('English')).toBeVisible(); + }); + + test('checks all the elements are visible with DSFR theme', async ({ + page, + }) => { + await overrideConfig(page, { + FRONTEND_THEME: 'dsfr', + }); + await page.goto('/'); + + const header = page.locator('header').first(); + + await expect(header.getByLabel('Docs Logo')).toBeVisible(); + await expect(header.locator('h2').getByText('Docs')).toHaveCSS( + 'font-family', + /Marianne/i, + ); + + await expect( + header.getByRole('button', { + name: 'Logout', + }), + ).toBeVisible(); + + await expect(header.getByText('English')).toBeVisible(); + + await expect( + header.getByRole('button', { + name: 'Les services de La Suite numรฉrique', + }), + ).toBeVisible(); + }); + + test('checks La Gauffre interaction', async ({ page }) => { + await overrideConfig(page, { + FRONTEND_THEME: 'dsfr', + }); + await page.goto('/'); + + const header = page.locator('header').first(); + + await expect( + header.getByRole('button', { + name: 'Les services de La Suite numรฉrique', + }), + ).toBeVisible(); + + /** + * La gaufre load a js file from a remote server, + * it takes some time to load the file and have the interaction available + */ + // eslint-disable-next-line playwright/no-wait-for-timeout + await page.waitForTimeout(1500); + + await header + .getByRole('button', { + name: 'Les services de La Suite numรฉrique', + }) + .click(); + + await expect( + page.getByRole('link', { name: 'France Transfert' }), + ).toBeVisible(); + + await expect(page.getByRole('link', { name: 'Grist' })).toBeVisible(); + }); +}); + +test.describe('Header mobile', () => { + test.use({ viewport: { width: 500, height: 1200 } }); + + test('it checks the header when mobile with DSFR theme', async ({ page }) => { + await overrideConfig(page, { + FRONTEND_THEME: 'dsfr', + }); + + await page.goto('/'); + + const header = page.locator('header').first(); + + await expect(header.getByLabel('Open the header menu')).toBeVisible(); + await expect(header.getByRole('link', { name: 'Docs Logo' })).toBeVisible(); + await expect( + header.getByRole('button', { + name: 'Les services de La Suite numรฉrique', + }), + ).toBeVisible(); + }); +}); + +test.describe('Header: Log out', () => { + test.use({ storageState: { cookies: [], origins: [] } }); + + // eslint-disable-next-line playwright/expect-expect + test('checks logout button', async ({ page, browserName }) => { + await page.goto('/'); + await keyCloakSignIn(page, browserName); + + await page + .getByRole('button', { + name: 'Logout', + }) + .click(); + + await expectLoginPage(page); + }); +}); diff --git a/src/frontend/apps/e2e/__tests__/app-conversations/home.spec.ts b/src/frontend/apps/e2e/__tests__/app-conversations/home.spec.ts new file mode 100644 index 0000000..7559b97 --- /dev/null +++ b/src/frontend/apps/e2e/__tests__/app-conversations/home.spec.ts @@ -0,0 +1,124 @@ +import { expect, test } from '@playwright/test'; + +import { overrideConfig } from './common'; + +test.beforeEach(async ({ page }) => { + await page.goto('/docs/'); +}); + +test.describe('Home page', () => { + test.use({ storageState: { cookies: [], origins: [] } }); + test('checks all the elements are visible', async ({ page }) => { + await page.goto('/docs/'); + + // Check header content + const header = page.locator('header').first(); + const footer = page.locator('footer').first(); + await expect(header).toBeVisible(); + await expect( + header.getByRole('button', { name: /Language/ }), + ).toBeVisible(); + await expect(header.getByRole('img', { name: 'Docs logo' })).toBeVisible(); + await expect(header.getByRole('heading', { name: 'Docs' })).toBeVisible(); + + // Check the titles + const h2 = page.locator('h2'); + await expect(h2.getByText('Govs โค๏ธ Open Source.')).toBeVisible(); + await expect( + h2.getByText('Collaborative writing, Simplified.'), + ).toBeVisible(); + await expect( + h2.getByText('An uncompromising writing experience.'), + ).toBeVisible(); + await expect( + h2.getByText('Simple and secure collaboration.'), + ).toBeVisible(); + await expect(h2.getByText('Flexible export.')).toBeVisible(); + await expect( + h2.getByText('A new way to organize knowledge.'), + ).toBeVisible(); + await expect( + page.getByRole('button', { name: 'Start Writing' }), + ).toBeVisible(); + + await expect(footer).toBeVisible(); + }); + + test('checks all the elements are visible with dsfr theme', async ({ + page, + }) => { + await overrideConfig(page, { + FRONTEND_THEME: 'dsfr', + theme_customization: { + footer: { + default: { + externalLinks: [ + { + label: 'legifrance.gouv.fr', + href: '#', + }, + ], + }, + }, + }, + }); + + await page.goto('/docs/'); + + // Check header content + const header = page.locator('header').first(); + const footer = page.locator('footer').first(); + await expect(header).toBeVisible(); + await expect( + header.getByRole('button', { name: /Language/ }), + ).toBeVisible(); + await expect( + header.getByRole('button', { name: 'Les services de La Suite numรฉ' }), + ).toBeVisible(); + await expect( + header.getByRole('img', { name: 'Gouvernement Logo' }), + ).toBeVisible(); + await expect(header.getByRole('img', { name: 'Docs logo' })).toBeVisible(); + await expect(header.getByRole('heading', { name: 'Docs' })).toBeVisible(); + await expect(header.getByText('BETA')).toBeVisible(); + + // Check the titles + const h2 = page.locator('h2'); + await expect(h2.getByText('Govs โค๏ธ Open Source.')).toBeVisible(); + await expect( + h2.getByText('Collaborative writing, Simplified.'), + ).toBeVisible(); + await expect( + h2.getByText('An uncompromising writing experience.'), + ).toBeVisible(); + await expect( + h2.getByText('Simple and secure collaboration.'), + ).toBeVisible(); + await expect(h2.getByText('Flexible export.')).toBeVisible(); + await expect( + h2.getByText('A new way to organize knowledge.'), + ).toBeVisible(); + + await expect( + page.getByText('Docs is already available, log in to use it now.'), + ).toBeVisible(); + await expect( + page.getByRole('button', { name: 'Proconnect Login' }), + ).toHaveCount(2); + + await expect(footer).toBeVisible(); + }); + + test('it checks the homepage feature flag', async ({ page }) => { + await overrideConfig(page, { + FRONTEND_HOMEPAGE_FEATURE_ENABLED: false, + }); + + await page.goto('/'); + + // Keyclock login page + await expect( + page.locator('.login-pf-page-header').getByText('conversations'), + ).toBeVisible(); + }); +}); diff --git a/src/frontend/apps/e2e/__tests__/app-conversations/language.spec.ts b/src/frontend/apps/e2e/__tests__/app-conversations/language.spec.ts new file mode 100644 index 0000000..0480208 --- /dev/null +++ b/src/frontend/apps/e2e/__tests__/app-conversations/language.spec.ts @@ -0,0 +1,150 @@ +import { Page, expect, test } from '@playwright/test'; + +import { createDoc } from './common'; + +test.describe.serial('Language', () => { + let page: Page; + + test.beforeAll(async ({ browser }) => { + page = await browser.newPage(); + }); + + test.afterAll(async () => { + await page.close(); + }); + + test.beforeEach(async ({ page }) => { + await page.goto('/'); + await waitForLanguageSwitch(page, TestLanguage.English); + }); + + test.afterEach(async ({ page }) => { + // Switch back to English - important for other tests to run as expected + await waitForLanguageSwitch(page, TestLanguage.English); + }); + + test('checks language switching', async ({ page }) => { + const header = page.locator('header').first(); + + // initial language should be english + await expect( + page.getByRole('button', { + name: 'New doc', + }), + ).toBeVisible(); + + // switch to french + await waitForLanguageSwitch(page, TestLanguage.French); + + await expect( + header.getByRole('button').getByText('Franรงais'), + ).toBeVisible(); + + await expect(page.getByLabel('Se dรฉconnecter')).toBeVisible(); + + await header.getByRole('button').getByText('Franรงais').click(); + await page.getByLabel('Deutsch').click(); + await expect(header.getByRole('button').getByText('Deutsch')).toBeVisible(); + + await expect(page.getByLabel('Abmelden')).toBeVisible(); + }); + + test('checks that backend uses the same language as the frontend', async ({ + page, + }) => { + // Helper function to intercept and assert 404 response + const check404Response = async (expectedDetail: string) => { + const interceptedBackendResponse = await page.request.get( + 'http://localhost:8071/api/v1.0/documents/non-existent-doc-uuid/', + ); + + // Assert that the intercepted error message is in the expected language + expect(await interceptedBackendResponse.json()).toStrictEqual({ + detail: expectedDetail, + }); + }; + + // Check for English 404 response + await check404Response('Not found.'); + + await waitForLanguageSwitch(page, TestLanguage.French); + + // Check for French 404 response + await check404Response('Pas trouvรฉ.'); + }); + + test('it check translations of the slash menu when changing language', async ({ + page, + browserName, + }) => { + await createDoc(page, 'doc-toolbar', browserName, 1); + + const header = page.locator('header').first(); + const editor = page.locator('.ProseMirror'); + // Trigger slash menu to show english menu + await editor.click(); + await editor.fill('/'); + await expect(page.getByText('Headings', { exact: true })).toBeVisible(); + await header.click(); + await expect(page.getByText('Headings', { exact: true })).toBeHidden(); + + // Reset menu + await editor.click(); + await editor.fill(''); + + // Change language to French + await waitForLanguageSwitch(page, TestLanguage.French); + + // Trigger slash menu to show french menu + await editor.click(); + await editor.fill('/'); + await expect(page.getByText('Titres', { exact: true })).toBeVisible(); + await header.click(); + await expect(page.getByText('Titres', { exact: true })).toBeHidden(); + }); +}); + +// language helper +export const TestLanguage = { + English: { + label: 'English', + expectedLocale: ['en-us'], + }, + French: { + label: 'Franรงais', + expectedLocale: ['fr-fr'], + }, + German: { + label: 'Deutsch', + expectedLocale: ['de-de'], + }, +} as const; + +type TestLanguageKey = keyof typeof TestLanguage; +type TestLanguageValue = (typeof TestLanguage)[TestLanguageKey]; + +export async function waitForLanguageSwitch( + page: Page, + lang: TestLanguageValue, +) { + const header = page.locator('header').first(); + const languagePicker = header.locator('.--docs--language-picker-text'); + const isAlreadyTargetLanguage = await languagePicker + .innerText() + .then((text) => text.toLowerCase().includes(lang.label.toLowerCase())); + + if (isAlreadyTargetLanguage) { + return; + } + + await languagePicker.click(); + const responsePromise = page.waitForResponse( + (resp) => + resp.url().includes('/user') && resp.request().method() === 'PATCH', + ); + await page.getByLabel(lang.label).click(); + const resolvedResponsePromise = await responsePromise; + const responseData = await resolvedResponsePromise.json(); + + expect(lang.expectedLocale).toContain(responseData.language); +} diff --git a/src/frontend/apps/e2e/__tests__/app-conversations/left-panel.spec.ts b/src/frontend/apps/e2e/__tests__/app-conversations/left-panel.spec.ts new file mode 100644 index 0000000..97523aa --- /dev/null +++ b/src/frontend/apps/e2e/__tests__/app-conversations/left-panel.spec.ts @@ -0,0 +1,48 @@ +import { expect, test } from '@playwright/test'; + +test.describe('Left panel desktop', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/'); + }); + + test('checks all the elements are visible', async ({ page }) => { + await expect(page.getByTestId('left-panel-desktop')).toBeVisible(); + await expect(page.getByTestId('left-panel-mobile')).toBeHidden(); + await expect(page.getByRole('button', { name: 'house' })).toBeVisible(); + await expect(page.getByRole('button', { name: 'New doc' })).toBeVisible(); + }); +}); + +test.describe('Left panel mobile', () => { + test.use({ viewport: { width: 500, height: 1200 } }); + + test.beforeEach(async ({ page }) => { + await page.goto('/'); + }); + + test('checks all the desktop elements are hidden and all mobile elements are visible', async ({ + page, + }) => { + await expect(page.getByTestId('left-panel-desktop')).toBeHidden(); + await expect(page.getByTestId('left-panel-mobile')).not.toBeInViewport(); + + const header = page.locator('header').first(); + const homeButton = page.getByRole('button', { name: 'house' }); + const newDocButton = page.getByRole('button', { name: 'New doc' }); + const languageButton = page.getByRole('button', { name: /Language/ }); + const logoutButton = page.getByRole('button', { name: 'Logout' }); + + await expect(homeButton).not.toBeInViewport(); + await expect(newDocButton).not.toBeInViewport(); + await expect(languageButton).not.toBeInViewport(); + await expect(logoutButton).not.toBeInViewport(); + + await header.getByLabel('Open the header menu').click(); + + await expect(page.getByTestId('left-panel-mobile')).toBeInViewport(); + await expect(homeButton).toBeInViewport(); + await expect(newDocButton).toBeInViewport(); + await expect(languageButton).toBeInViewport(); + await expect(logoutButton).toBeInViewport(); + }); +}); diff --git a/src/frontend/apps/e2e/__tests__/helpers.ts b/src/frontend/apps/e2e/__tests__/helpers.ts new file mode 100644 index 0000000..678b555 --- /dev/null +++ b/src/frontend/apps/e2e/__tests__/helpers.ts @@ -0,0 +1,21 @@ +import { Locator } from '@playwright/test'; + +export async function waitForElementCount( + locator: Locator, + count: number, + timeout: number, +) { + let elapsedTime = 0; + const interval = 200; // Check every 200 ms + while (elapsedTime < timeout) { + const currentCount = await locator.count(); + if (currentCount >= count) { + return true; + } + await locator.page().waitForTimeout(interval); // Wait for the interval before checking again + elapsedTime += interval; + } + throw new Error( + `Timeout after ${timeout}ms waiting for element count to be at least ${count}`, + ); +} diff --git a/src/frontend/apps/e2e/package.json b/src/frontend/apps/e2e/package.json new file mode 100644 index 0000000..d617105 --- /dev/null +++ b/src/frontend/apps/e2e/package.json @@ -0,0 +1,25 @@ +{ + "name": "app-e2e", + "version": "0.0.1", + "private": true, + "scripts": { + "lint": "eslint . --ext .ts", + "install-playwright": "playwright install --with-deps", + "test": "playwright test", + "test:ui": "yarn test --ui", + "test:ui::firefox": "yarn test:ui --project=firefox", + "test:ui::webkit": "yarn test:ui --project=webkit", + "test:ui::chromium": "yarn test:ui --project=chromium" + }, + "devDependencies": { + "@playwright/test": "1.52.0", + "@types/node": "*", + "@types/pdf-parse": "1.1.5", + "eslint-config-conversations": "*", + "typescript": "*" + }, + "dependencies": { + "convert-stream": "1.0.2", + "pdf-parse": "1.1.1" + } +} diff --git a/src/frontend/apps/e2e/playwright.config.ts b/src/frontend/apps/e2e/playwright.config.ts new file mode 100644 index 0000000..71904a1 --- /dev/null +++ b/src/frontend/apps/e2e/playwright.config.ts @@ -0,0 +1,81 @@ +import { defineConfig, devices } from '@playwright/test'; + +const PORT = process.env.PORT || 3000; + +const baseURL = `http://localhost:${PORT}`; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + // Timeout per test + timeout: 30 * 1000, + testDir: './__tests__', + outputDir: './test-results', + + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + maxFailures: process.env.CI ? 3 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 3 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: [['html', { outputFolder: './report' }]], + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + baseURL, + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + webServer: { + command: !process.env.CI ? `cd ../.. && yarn app:dev --port ${PORT}` : '', + url: baseURL, + timeout: 120 * 1000, + reuseExistingServer: true, + }, + globalSetup: require.resolve('./__tests__/app-conversations/auth.setup'), + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { + ...devices['Desktop Chrome'], + locale: 'en-US', + timezoneId: 'Europe/Paris', + storageState: 'playwright/.auth/user-chromium.json', + contextOptions: { + permissions: ['clipboard-read', 'clipboard-write'], + }, + }, + }, + { + name: 'webkit', + use: { + ...devices['Desktop Safari'], + locale: 'en-US', + timezoneId: 'Europe/Paris', + storageState: 'playwright/.auth/user-webkit.json', + }, + }, + { + name: 'firefox', + use: { + ...devices['Desktop Firefox'], + locale: 'en-US', + timezoneId: 'Europe/Paris', + storageState: 'playwright/.auth/user-firefox.json', + launchOptions: { + firefoxUserPrefs: { + 'dom.events.asyncClipboard.readText': true, + 'dom.events.testing.asyncClipboard': true, + }, + }, + }, + }, + ], +}); diff --git a/src/frontend/apps/e2e/tsconfig.json b/src/frontend/apps/e2e/tsconfig.json new file mode 100644 index 0000000..a9aa481 --- /dev/null +++ b/src/frontend/apps/e2e/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + }, + "include": ["**/*.ts", "**/*.d.ts"], + "exclude": ["node_modules"] +} diff --git a/src/frontend/apps/e2e/type/convert-stream.d.ts b/src/frontend/apps/e2e/type/convert-stream.d.ts new file mode 100644 index 0000000..d79cbcc --- /dev/null +++ b/src/frontend/apps/e2e/type/convert-stream.d.ts @@ -0,0 +1,5 @@ +declare module 'convert-stream' { + export function toBuffer( + readableStream: NodeJS.ReadableStream, + ): Promise<Buffer>; +} diff --git a/src/frontend/package.json b/src/frontend/package.json new file mode 100644 index 0000000..7ad34e6 --- /dev/null +++ b/src/frontend/package.json @@ -0,0 +1,39 @@ +{ + "name": "conversations", + "version": "0.0.1", + "private": true, + "workspaces": { + "packages": [ + "apps/*", + "packages/*", + "servers/*" + ] + }, + "scripts": { + "APP_CONVERSATIONS": "yarn workspace app-conversations", + "APP_E2E": "yarn workspace app-e2e", + "I18N": "yarn workspace packages-i18n", + "app:dev": "yarn APP_CONVERSATIONS run dev", + "app:start": "yarn APP_CONVERSATIONS run start", + "app:build": "yarn APP_CONVERSATIONS run build", + "app:test": "yarn APP_CONVERSATIONS run test", + "ci:build": "yarn APP_CONVERSATIONS run build:ci", + "e2e:test": "yarn APP_E2E run test", + "lint": "yarn APP_CONVERSATIONS run lint --fix && yarn APP_E2E run lint --fix && yarn workspace eslint-config-conversations run lint --fix && yarn I18N run lint --fix", + "i18n:extract": "yarn I18N run extract-translation", + "i18n:deploy": "yarn I18N run format-deploy && yarn APP_CONVERSATIONS prettier", + "i18n:test": "yarn I18N run test", + "test": "yarn server:test && yarn app:test" + }, + "resolutions": { + "@types/node": "22.15.29", + "@types/react": "19.1.6", + "@types/react-dom": "19.1.6", + "@typescript-eslint/eslint-plugin": "8.33.1", + "@typescript-eslint/parser": "8.33.1", + "eslint": "8.57.0", + "react": "19.1.0", + "react-dom": "19.1.0", + "typescript": "5.8.3" + } +} diff --git a/src/frontend/packages/eslint-config-conversations/.eslintrc.js b/src/frontend/packages/eslint-config-conversations/.eslintrc.js new file mode 100644 index 0000000..76b342e --- /dev/null +++ b/src/frontend/packages/eslint-config-conversations/.eslintrc.js @@ -0,0 +1,21 @@ +const common = require('./common'); + +module.exports = { + root: true, + settings: { + react: { + version: 'detect', + }, + }, + extends: [ + 'plugin:prettier/recommended', + 'plugin:import/recommended', + 'plugin:react/recommended', + ], + rules: common.globalRules, + parserOptions: { + sourceType: 'module', + ecmaVersion: 'latest', + }, + ignorePatterns: ['node_modules'], +}; diff --git a/src/frontend/packages/eslint-config-conversations/common.js b/src/frontend/packages/eslint-config-conversations/common.js new file mode 100644 index 0000000..3dd2535 --- /dev/null +++ b/src/frontend/packages/eslint-config-conversations/common.js @@ -0,0 +1,76 @@ +const eslintTS = [ + { + files: ['*.ts', '*.tsx'], + extends: [ + 'plugin:@typescript-eslint/recommended', + 'plugin:@typescript-eslint/recommended-requiring-type-checking', + ], + parser: '@typescript-eslint/parser', // Specifies the ESLint parser + parserOptions: { + tsconfigRootDir: __dirname, + project: ['./tsconfig.json'], + }, + rules: { + '@typescript-eslint/no-explicit-any': 'error', + '@typescript-eslint/no-non-null-assertion': 'error', + '@typescript-eslint/no-unused-vars': [ + 'error', + { varsIgnorePattern: '^_', argsIgnorePattern: '^_' }, + ], + 'sort-imports': [ + 'error', + { + ignoreDeclarationSort: true, + }, + ], + }, + }, + { + files: ['*.d.ts'], + rules: { + 'no-unused-vars': 'off', + }, + }, +]; + +const globalRules = { + 'block-scoped-var': 'error', + curly: ['error', 'all'], + 'import/no-duplicates': ['error', { considerQueryString: false }], + 'import/order': [ + 'error', + { + alphabetize: { + order: 'asc', + }, + groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'], + pathGroups: [ + { + pattern: '@/**', + group: 'internal', + }, + ], + pathGroupsExcludedImportTypes: ['builtin'], + 'newlines-between': 'always', + warnOnUnassignedImports: true, + }, + ], + 'no-alert': 'error', + 'no-unused-vars': [ + 'error', + { varsIgnorePattern: '^_', argsIgnorePattern: '^_' }, + ], + 'no-var': 'error', + 'react/jsx-curly-brace-presence': [ + 'error', + { props: 'never', children: 'never', propElementValues: 'always' }, + ], + 'sort-imports': [ + 'error', + { + ignoreDeclarationSort: true, + }, + ], +}; + +module.exports = { eslintTS, globalRules }; diff --git a/src/frontend/packages/eslint-config-conversations/jest.js b/src/frontend/packages/eslint-config-conversations/jest.js new file mode 100644 index 0000000..93a5f2c --- /dev/null +++ b/src/frontend/packages/eslint-config-conversations/jest.js @@ -0,0 +1,51 @@ +const common = require('./common'); + +module.exports = { + extends: ['plugin:prettier/recommended', 'plugin:react/recommended'], + rules: common.globalRules, + settings: { + react: { + version: 'detect', + }, + }, + overrides: [ + ...common.eslintTS, + { + files: ['*.spec.*', '*.test.*', '**/__mock__/**/*'], + plugins: ['jest'], + extends: ['plugin:jest/recommended', 'plugin:testing-library/react'], + rules: { + '@typescript-eslint/ban-types': 'off', + '@typescript-eslint/no-empty-function': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/no-unsafe-argument': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-call': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unsafe-return': 'off', + 'testing-library/no-await-sync-events': [ + 'error', + { eventModules: ['fire-event'] }, + ], + 'testing-library/await-async-events': [ + 'error', + { + eventModule: 'userEvent', + }, + ], + 'testing-library/no-manual-cleanup': 'off', + '@typescript-eslint/no-unused-vars': [ + 'error', + { varsIgnorePattern: '^_', argsIgnorePattern: '^_' }, + ], + 'react/display-name': 0, + 'jest/expect-expect': 'error', + '@typescript-eslint/unbound-method': 'off', + 'jest/unbound-method': 'error', + 'react/react-in-jsx-scope': 'off', + }, + }, + ], + ignorePatterns: ['node_modules'], +}; diff --git a/src/frontend/packages/eslint-config-conversations/next.js b/src/frontend/packages/eslint-config-conversations/next.js new file mode 100644 index 0000000..b2922eb --- /dev/null +++ b/src/frontend/packages/eslint-config-conversations/next.js @@ -0,0 +1,40 @@ +const common = require('./common'); + +module.exports = { + extends: [ + 'next', + 'plugin:prettier/recommended', + 'plugin:@tanstack/eslint-plugin-query/recommended', + 'plugin:jsx-a11y/recommended', + ], + parserOptions: { + babelOptions: { + presets: [require.resolve('next/babel')], + }, + }, + settings: { + 'jsx-a11y': { + polymorphicPropName: 'as', + components: { + Input: 'input', + Button: 'button', + Box: 'div', + Text: 'span', + Select: 'select', + }, + }, + }, + rules: { + ...common.globalRules, + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'error', + }, + overrides: [ + ...common.eslintTS, + { + files: ['*.spec.*', '*.test.*', '**/__mock__/**/*'], + extends: ['conversations/jest'], + }, + ], + ignorePatterns: ['node_modules'], +}; diff --git a/src/frontend/packages/eslint-config-conversations/package.json b/src/frontend/packages/eslint-config-conversations/package.json new file mode 100644 index 0000000..75d9d9b --- /dev/null +++ b/src/frontend/packages/eslint-config-conversations/package.json @@ -0,0 +1,24 @@ +{ + "name": "eslint-config-conversations", + "version": "0.0.1", + "license": "MIT", + "scripts": { + "lint": "eslint --ext .js ." + }, + "dependencies": { + "@next/eslint-plugin-next": "15.3.3", + "@tanstack/eslint-plugin-query": "5.78.0", + "@typescript-eslint/eslint-plugin": "*", + "@typescript-eslint/parser": "*", + "eslint": "*", + "eslint-config-next": "15.3.3", + "eslint-config-prettier": "10.1.5", + "eslint-plugin-import": "2.31.0", + "eslint-plugin-jest": "28.12.0", + "eslint-plugin-jsx-a11y": "6.10.2", + "eslint-plugin-playwright": "2.2.0", + "eslint-plugin-prettier": "5.4.1", + "eslint-plugin-testing-library": "7.4.0", + "prettier": "3.5.3" + } +} diff --git a/src/frontend/packages/eslint-config-conversations/playwright.js b/src/frontend/packages/eslint-config-conversations/playwright.js new file mode 100644 index 0000000..c51edc5 --- /dev/null +++ b/src/frontend/packages/eslint-config-conversations/playwright.js @@ -0,0 +1,36 @@ +const common = require('./common'); + +module.exports = { + extends: ['next', 'plugin:prettier/recommended'], + settings: { + react: { + version: 'detect', + }, + }, + parserOptions: { + babelOptions: { + presets: [require.resolve('next/babel')], + }, + }, + rules: { ...common.globalRules, '@next/next/no-html-link-for-pages': 'off' }, + overrides: [ + ...common.eslintTS, + { + files: ['**/*.ts'], + rules: { + '@typescript-eslint/no-unsafe-member-access': 'off', + }, + }, + { + files: ['*.spec.*', '*.test.*', '**/__mock__/**/*'], + extends: ['plugin:playwright/recommended'], + plugins: ['playwright'], + rules: { + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + }, + }, + ], + ignorePatterns: ['node_modules'], +}; diff --git a/src/frontend/packages/i18n/.eslintrc.js b/src/frontend/packages/i18n/.eslintrc.js new file mode 100644 index 0000000..51162ce --- /dev/null +++ b/src/frontend/packages/i18n/.eslintrc.js @@ -0,0 +1,11 @@ +module.exports = { + root: true, + extends: ['conversations/jest', 'plugin:import/recommended'], + parserOptions: { + sourceType: 'module', + ecmaVersion: 'latest', + tsconfigRootDir: __dirname, + project: ['./tsconfig.json'], + }, + ignorePatterns: ['node_modules'], +}; diff --git a/src/frontend/packages/i18n/.gitignore b/src/frontend/packages/i18n/.gitignore new file mode 100644 index 0000000..a306801 --- /dev/null +++ b/src/frontend/packages/i18n/.gitignore @@ -0,0 +1 @@ +locales diff --git a/src/frontend/packages/i18n/__tests__/i18n.test.ts b/src/frontend/packages/i18n/__tests__/i18n.test.ts new file mode 100644 index 0000000..46a432f --- /dev/null +++ b/src/frontend/packages/i18n/__tests__/i18n.test.ts @@ -0,0 +1,104 @@ +import { execSync } from 'child_process'; +import fs from 'fs'; +import path from 'path'; + +describe('integration testing on i18n package', () => { + afterAll(() => { + fs.rmSync('./locales/tests', { recursive: true, force: true }); + }); + + test('cmd extract-translation:conversations', () => { + // To be sure the file is not here + fs.rmSync('./locales/conversations/translations-crowdin.json', { + recursive: true, + force: true, + }); + expect( + fs.existsSync('./locales/conversations/translations-crowdin.json'), + ).toBeFalsy(); + + // Generate the file + execSync('yarn extract-translation:conversations'); + expect( + fs.existsSync('./locales/conversations/translations-crowdin.json'), + ).toBeTruthy(); + }); + + test('cmd format-deploy', () => { + // To be sure the tests folder is not here + fs.rmSync('./locales/tests', { recursive: true, force: true }); + expect(fs.existsSync('./locales/tests')).toBeFalsy(); + + // Generate english json file + fs.mkdirSync('./locales/tests/en/', { recursive: true }); + fs.writeFileSync( + './locales/tests/en/translations.json', + JSON.stringify({ test: { message: 'My test' } }), + 'utf8', + ); + expect(fs.existsSync('./locales/tests/en/translations.json')).toBeTruthy(); + + fs.mkdirSync('./locales/tests/fr/', { recursive: true }); + fs.writeFileSync( + './locales/tests/fr/translations.json', + JSON.stringify({ test: { message: 'Mon test' } }), + 'utf8', + ); + expect(fs.existsSync('./locales/tests/fr/translations.json')).toBeTruthy(); + + // Execute format-deploy command + const output = './locales/tests/translations.json'; + execSync(`node ./format-deploy.mjs --app=tests --output=${output}`); + const json = JSON.parse(fs.readFileSync(output, 'utf8')); + expect(json).toEqual({ + en: { + translation: { test: 'My test' }, + }, + fr: { + translation: { test: 'Mon test' }, + }, + }); + }); + + test('cmd format-deploy throws an error when translation file is not found', () => { + // To be sure the tests folder is not here + fs.rmSync('./locales/tests', { recursive: true, force: true }); + expect(fs.existsSync('./locales/tests')).toBeFalsy(); + + // Generate english json file + fs.mkdirSync('./locales/tests/en/', { recursive: true }); + + // Execute format-deploy command + const output = './locales/tests/translations.json'; + + const cmd = () => { + execSync(`node ./format-deploy.mjs --app=tests --output=${output}`, { + stdio: 'pipe', + }); + }; + + expect(cmd).toThrow( + `Error: File locales${path.sep}tests${path.sep}en${path.sep}translations.json not found!`, + ); + }); + + test('cmd format-deploy throws an error when no translation to deploy', () => { + // To be sure the tests folder is not here + fs.rmSync('./locales/tests', { recursive: true, force: true }); + expect(fs.existsSync('./locales/tests')).toBeFalsy(); + + // Generate english json file + fs.mkdirSync('./locales/tests/', { recursive: true }); + + // Execute format-deploy command + const output = './locales/tests/translations.json'; + + const cmd = () => { + execSync(`node ./format-deploy.mjs --app=tests --output=${output}`, { + stdio: 'pipe', + }); + }; + + expect(cmd).toThrow('Error: No translation to deploy'); + }); +}); diff --git a/src/frontend/packages/i18n/__tests__/translations.test.ts b/src/frontend/packages/i18n/__tests__/translations.test.ts new file mode 100644 index 0000000..e829802 --- /dev/null +++ b/src/frontend/packages/i18n/__tests__/translations.test.ts @@ -0,0 +1,52 @@ +import { execSync } from 'child_process'; +import fs from 'fs'; + +describe('checks all the frontend translation are made', () => { + it('checks missing translation. If this test fails, go to https://crowdin.com/', () => { + // Extract the translations + execSync( + 'yarn extract-translation:conversations -c ./i18next-parser.config.jest.mjs', + ); + const outputCrowdin = './locales/conversations/translations-crowdin.json'; + const jsonCrowdin = JSON.parse(fs.readFileSync(outputCrowdin, 'utf8')); + const listKeysCrowdin = Object.keys(jsonCrowdin).sort(); + + // Check the translations in the app conversations + const outputconversations = + '../../apps/conversations/src/i18n/translations.json'; + const jsonconversations = JSON.parse( + fs.readFileSync(outputconversations, 'utf8'), + ); + + // Our keys are in english, so we don't need to check the english translation + Object.keys(jsonconversations) + .filter((key) => key !== 'en') + .forEach((key) => { + const listKeysconversations = Object.keys( + jsonconversations[key].translation, + ).sort(); + const missingKeys = listKeysCrowdin.filter( + (element) => !listKeysconversations.includes(element), + ); + const additionalKeys = listKeysconversations.filter( + (element) => !listKeysCrowdin.includes(element), + ); + + if (missingKeys.length > 0) { + console.log( + `Missing keys in conversations translations that should be translated in Crowdin, got to https://crowdin.com/ :`, + missingKeys, + ); + } + + if (additionalKeys.length > 0) { + console.log( + `Additional keys in conversations translations that seems not present in this branch:`, + additionalKeys, + ); + } + + expect(missingKeys.length).toBe(0); + }); + }); +}); diff --git a/src/frontend/packages/i18n/format-deploy.mjs b/src/frontend/packages/i18n/format-deploy.mjs new file mode 100644 index 0000000..ccd3ee3 --- /dev/null +++ b/src/frontend/packages/i18n/format-deploy.mjs @@ -0,0 +1,55 @@ +import fs from 'fs'; +import path from 'path'; + +import yargs from 'yargs'; + +// Get our args +const argv = yargs(process.argv).argv; +const { app, output } = argv; + +const folderPath = './locales/' + app; +const namefile = 'translations.json'; +const jsonI18n = {}; + +// Fetch the files in the locales folder +fs.readdirSync(folderPath).map((language) => { + const languagePath = path.join(folderPath, path.sep, language); + // Crowdin output file in folder, we want to treat only these ones + if (!fs.lstatSync(languagePath).isDirectory()) { + return; + } + + jsonI18n[language] = { + translation: {}, + }; + + // Get the json file generated by crowdin + const pathTranslateFile = path.join(languagePath, path.sep, namefile); + + if (!fs.existsSync(pathTranslateFile)) { + throw new Error(`File ${pathTranslateFile} not found!`); + } + + const json = JSON.parse(fs.readFileSync(pathTranslateFile, 'utf8')); + + // Transform the json file to the format expected by i18next + const jsonKeyMessage = {}; + Object.keys(json) + .sort() + .forEach((key) => { + jsonKeyMessage[key] = json[key].message; + }); + + jsonI18n[language] = { + translation: jsonKeyMessage, + }; +}); + +if (!Object.keys(jsonI18n).length) { + throw new Error(`No translation to deploy`); +} + +// Write the file to the output +fs.writeFileSync(output, JSON.stringify(jsonI18n), 'utf8'); + +console.log(`${app} translations deployed!`); diff --git a/src/frontend/packages/i18n/i18next-parser.config.jest.mjs b/src/frontend/packages/i18n/i18next-parser.config.jest.mjs new file mode 100644 index 0000000..ad2126c --- /dev/null +++ b/src/frontend/packages/i18n/i18next-parser.config.jest.mjs @@ -0,0 +1,10 @@ +const config = { + customValueTemplate: { + message: '${key}', + description: '${description}', + }, + keepRemoved: false, + keySeparator: false, +}; + +export default config; diff --git a/src/frontend/packages/i18n/i18next-parser.config.mjs b/src/frontend/packages/i18n/i18next-parser.config.mjs new file mode 100644 index 0000000..506129c --- /dev/null +++ b/src/frontend/packages/i18n/i18next-parser.config.mjs @@ -0,0 +1,12 @@ +const config = { + customValueTemplate: { + message: '${key}', + description: '${description}', + }, + keepRemoved: false, + keySeparator: false, + nsSeparator: false, + namespaceSeparator: false, +}; + +export default config; diff --git a/src/frontend/packages/i18n/jest.config.ts b/src/frontend/packages/i18n/jest.config.ts new file mode 100644 index 0000000..c670713 --- /dev/null +++ b/src/frontend/packages/i18n/jest.config.ts @@ -0,0 +1,7 @@ +export default { + rootDir: './', + testEnvironment: 'node', + transform: { + '^.+\\.(ts)$': 'ts-jest', + }, +}; diff --git a/src/frontend/packages/i18n/package.json b/src/frontend/packages/i18n/package.json new file mode 100644 index 0000000..0a35884 --- /dev/null +++ b/src/frontend/packages/i18n/package.json @@ -0,0 +1,25 @@ +{ + "name": "packages-i18n", + "version": "0.0.1", + "private": true, + "scripts": { + "extract-translation": "yarn extract-translation:conversations", + "extract-translation:conversations": "yarn i18next ../../apps/conversations/**/*.{ts,tsx} -c ./i18next-parser.config.mjs -o ./locales/conversations/translations-crowdin.json", + "format-deploy": "yarn format-deploy:conversations", + "format-deploy:conversations": "node ./format-deploy.mjs --app=conversations --output=../../apps/conversations/src/i18n/translations.json", + "format-rebuild-fr:conversations": "node ./rebuild-translations.mjs --language=fr --app=conversations --output=../../apps/conversations/src/i18n/translations.json", + "lint": "eslint --ext .js,.ts,.mjs .", + "test": "jest" + }, + "dependencies": { + "@types/jest": "29.5.14", + "@types/node": "*", + "eslint-config-conversations": "*", + "eslint-plugin-import": "2.31.0", + "i18next-parser": "9.3.0", + "jest": "29.7.0", + "ts-jest": "29.3.4", + "typescript": "*", + "yargs": "18.0.0" + } +} diff --git a/src/frontend/packages/i18n/rebuild-translations.mjs b/src/frontend/packages/i18n/rebuild-translations.mjs new file mode 100644 index 0000000..4f19b77 --- /dev/null +++ b/src/frontend/packages/i18n/rebuild-translations.mjs @@ -0,0 +1,49 @@ +/** + * For some unexpected reasons it can happen that the translations in Crowdin are lost. + * If that happens, we can rebuild the Crowdin translations file from our translated json file. + * + * This script is used to rebuild the translations file from the empty skeleton file generated by Crowdin. + */ + +import fs from 'fs'; +import path from 'path'; + +import yargs from 'yargs'; + +// Get our args +const argv = yargs(process.argv).argv; +const { app, output, language } = argv; + +const folderPath = './locales/' + app; +const namefile = 'translations-skeleton.json'; +const nameRebuildfile = 'translations-rebuild.json'; + +const pathRebuildFile = path.join(folderPath, path.sep, nameRebuildfile); + +// Get the skeleton generated from crowdin +const pathSkeletonFile = path.join(folderPath, path.sep, namefile); + +if (!fs.existsSync(pathSkeletonFile)) { + throw new Error(`File ${pathSkeletonFile} not found!`); +} + +// Get the translated file +if (!fs.existsSync(output)) { + throw new Error(`File ${output} not found!`); +} + +const jsonSkel = JSON.parse(fs.readFileSync(pathSkeletonFile, 'utf8')); +const jsonTrans = JSON.parse(fs.readFileSync(output, 'utf8')); + +// Transform the json file to the format expected by i18next +const jsonRebuild = jsonSkel; +Object.keys(jsonSkel) + .sort() + .forEach((key) => { + jsonRebuild[key]['message'] = jsonTrans[language]['translation'][key] || ''; + }); + +// Write the file to the output +fs.writeFileSync(pathRebuildFile, JSON.stringify(jsonRebuild), 'utf8'); + +console.log(`${app} translations rebuild!`); diff --git a/src/frontend/packages/i18n/tsconfig.json b/src/frontend/packages/i18n/tsconfig.json new file mode 100644 index 0000000..c4350f5 --- /dev/null +++ b/src/frontend/packages/i18n/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + }, + "include": [ + "**/*.ts", + ], + "exclude": ["node_modules"] +} diff --git a/src/frontend/yarn.lock b/src/frontend/yarn.lock new file mode 100644 index 0000000..dc130a2 --- /dev/null +++ b/src/frontend/yarn.lock @@ -0,0 +1,14018 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@adobe/css-tools@^4.4.0": + version "4.4.2" + resolved "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.2.tgz" + integrity sha512-baYZExFpsdkBNuvGKTKWCwKH57HRZLVtycZS05WTQNVOiXVSeAki3nU35zlRbToeMW8aHlJfyS+1C4BOv27q0A== + +"@ag-media/react-pdf-table@2.0.3": + version "2.0.3" + resolved "https://registry.npmjs.org/@ag-media/react-pdf-table/-/react-pdf-table-2.0.3.tgz" + integrity sha512-IscjfAOKwsyQok9YmzvuToe6GojN7J8hF0kb8C+K8qZX1DvhheGO+hRSAPxbv2nKMbSpvk7CIhSqJEkw++XVWg== + +"@ai-sdk/provider-utils@2.2.8": + version "2.2.8" + resolved "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.8.tgz" + integrity sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA== + dependencies: + "@ai-sdk/provider" "1.1.3" + nanoid "^3.3.8" + secure-json-parse "^2.7.0" + +"@ai-sdk/provider@1.1.3": + version "1.1.3" + resolved "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.1.3.tgz" + integrity sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg== + dependencies: + json-schema "^0.4.0" + +"@ai-sdk/react@1.2.12": + version "1.2.12" + resolved "https://registry.npmjs.org/@ai-sdk/react/-/react-1.2.12.tgz" + integrity sha512-jK1IZZ22evPZoQW3vlkZ7wvjYGYF+tRBKXtrcolduIkQ/m/sOAVcVeVDUDvh1T91xCnWCdUGCPZg2avZ90mv3g== + dependencies: + "@ai-sdk/provider-utils" "2.2.8" + "@ai-sdk/ui-utils" "1.2.11" + swr "^2.2.5" + throttleit "2.1.0" + +"@ai-sdk/ui-utils@1.2.11": + version "1.2.11" + resolved "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-1.2.11.tgz" + integrity sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w== + dependencies: + "@ai-sdk/provider" "1.1.3" + "@ai-sdk/provider-utils" "2.2.8" + zod-to-json-schema "^3.24.1" + +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@apideck/better-ajv-errors@^0.3.1": + version "0.3.6" + resolved "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz" + integrity sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA== + dependencies: + json-schema "^0.4.0" + jsonpointer "^5.0.0" + leven "^3.1.0" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz" + integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== + dependencies: + "@babel/helper-validator-identifier" "^7.27.1" + js-tokens "^4.0.0" + picocolors "^1.1.1" + +"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.27.2": + version "7.27.2" + resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.2.tgz" + integrity sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ== + +"@babel/core@^7.0.0", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.18.5", "@babel/core@^7.21.3", "@babel/core@^7.23.9", "@babel/core@^7.24.4": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz" + integrity sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.27.1" + "@babel/helper-compilation-targets" "^7.27.1" + "@babel/helper-module-transforms" "^7.27.1" + "@babel/helpers" "^7.27.1" + "@babel/parser" "^7.27.1" + "@babel/template" "^7.27.1" + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + 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.27.1", "@babel/generator@^7.7.2": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz" + integrity sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w== + dependencies: + "@babel/parser" "^7.27.1" + "@babel/types" "^7.27.1" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^3.0.2" + +"@babel/helper-annotate-as-pure@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.1.tgz" + integrity sha512-WnuuDILl9oOBbKnb4L+DyODx7iC47XfzmNCpTttFsSp6hTG7XZxu60+4IO+2/hPfcGOoKbFiwoI/+zwARbNQow== + dependencies: + "@babel/types" "^7.27.1" + +"@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.27.1", "@babel/helper-compilation-targets@^7.27.2": + version "7.27.2" + resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz" + integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== + dependencies: + "@babel/compat-data" "^7.27.2" + "@babel/helper-validator-option" "^7.27.1" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-create-class-features-plugin@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz" + integrity sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.1" + "@babel/helper-member-expression-to-functions" "^7.27.1" + "@babel/helper-optimise-call-expression" "^7.27.1" + "@babel/helper-replace-supers" "^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + "@babel/traverse" "^7.27.1" + semver "^6.3.1" + +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz" + integrity sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.1" + regexpu-core "^6.2.0" + semver "^6.3.1" + +"@babel/helper-define-polyfill-provider@^0.6.3", "@babel/helper-define-polyfill-provider@^0.6.4": + version "0.6.4" + resolved "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.4.tgz" + integrity sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw== + 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-member-expression-to-functions@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz" + integrity sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz" + integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/helper-module-transforms@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz" + integrity sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g== + dependencies: + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@babel/traverse" "^7.27.1" + +"@babel/helper-optimise-call-expression@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz" + integrity sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw== + dependencies: + "@babel/types" "^7.27.1" + +"@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.27.1", "@babel/helper-plugin-utils@^7.8.0": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz" + integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== + +"@babel/helper-remap-async-to-generator@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz" + integrity sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.1" + "@babel/helper-wrap-function" "^7.27.1" + "@babel/traverse" "^7.27.1" + +"@babel/helper-replace-supers@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz" + integrity sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.27.1" + "@babel/helper-optimise-call-expression" "^7.27.1" + "@babel/traverse" "^7.27.1" + +"@babel/helper-skip-transparent-expression-wrappers@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz" + integrity sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + +"@babel/helper-validator-identifier@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz" + integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== + +"@babel/helper-validator-option@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + +"@babel/helper-wrap-function@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz" + integrity sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ== + dependencies: + "@babel/template" "^7.27.1" + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/helpers@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz" + integrity sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ== + dependencies: + "@babel/template" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.27.1", "@babel/parser@^7.27.2": + version "7.27.2" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz" + integrity sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw== + dependencies: + "@babel/types" "^7.27.1" + +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz" + integrity sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/traverse" "^7.27.1" + +"@babel/plugin-bugfix-safari-class-field-initializer-scope@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz" + integrity sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz" + integrity sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz" + integrity sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + "@babel/plugin-transform-optional-chaining" "^7.27.1" + +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.27.1.tgz" + integrity sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/traverse" "^7.27.1" + +"@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.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz" + integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz" + 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.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-import-assertions@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz" + integrity sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-syntax-import-attributes@^7.24.7", "@babel/plugin-syntax-import-attributes@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz" + integrity sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz" + 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.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.27.1", "@babel/plugin-syntax-jsx@^7.7.2": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz" + integrity sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz" + 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.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz" + 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.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz" + 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.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz" + 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.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz" + 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.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz" + 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.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz" + 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.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.27.1", "@babel/plugin-syntax-typescript@^7.7.2": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz" + integrity sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-syntax-unicode-sets-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz" + 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.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz" + integrity sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-async-generator-functions@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.27.1.tgz" + integrity sha512-eST9RrwlpaoJBDHShc+DS2SG4ATTi2MYNb4OxYkf3n+7eb49LWpnS+HSpVfW4x927qQwgk8A2hGNVaajAEw0EA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-remap-async-to-generator" "^7.27.1" + "@babel/traverse" "^7.27.1" + +"@babel/plugin-transform-async-to-generator@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz" + integrity sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA== + dependencies: + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-remap-async-to-generator" "^7.27.1" + +"@babel/plugin-transform-block-scoped-functions@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz" + integrity sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-block-scoping@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.1.tgz" + integrity sha512-QEcFlMl9nGTgh1rn2nIeU5bkfb9BAjaQcWbiP4LvKxUot52ABcTkpcyJ7f2Q2U2RuQ84BNLgts3jRme2dTx6Fw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-class-properties@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz" + integrity sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-class-static-block@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.27.1.tgz" + integrity sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-classes@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.27.1.tgz" + integrity sha512-7iLhfFAubmpeJe/Wo2TVuDrykh/zlWXLzPNdL0Jqn/Xu8R3QQ8h9ff8FQoISZOsw74/HFqFI7NX63HN7QFIHKA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.1" + "@babel/helper-compilation-targets" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-replace-supers" "^7.27.1" + "@babel/traverse" "^7.27.1" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz" + integrity sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/template" "^7.27.1" + +"@babel/plugin-transform-destructuring@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.27.1.tgz" + integrity sha512-ttDCqhfvpE9emVkXbPD8vyxxh4TWYACVybGkDj+oReOGwnp066ITEivDlLwe0b1R0+evJ13IXQuLNB5w1fhC5Q== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-dotall-regex@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz" + integrity sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-duplicate-keys@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz" + integrity sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz" + integrity sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-dynamic-import@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz" + integrity sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-exponentiation-operator@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz" + integrity sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-export-namespace-from@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz" + integrity sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-for-of@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz" + integrity sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + +"@babel/plugin-transform-function-name@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz" + integrity sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ== + dependencies: + "@babel/helper-compilation-targets" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/traverse" "^7.27.1" + +"@babel/plugin-transform-json-strings@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz" + integrity sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-literals@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz" + integrity sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-logical-assignment-operators@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz" + integrity sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-member-expression-literals@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz" + integrity sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-modules-amd@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz" + integrity sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA== + dependencies: + "@babel/helper-module-transforms" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-modules-commonjs@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz" + integrity sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw== + dependencies: + "@babel/helper-module-transforms" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-modules-systemjs@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz" + integrity sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA== + dependencies: + "@babel/helper-module-transforms" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@babel/traverse" "^7.27.1" + +"@babel/plugin-transform-modules-umd@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz" + integrity sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w== + dependencies: + "@babel/helper-module-transforms" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz" + integrity sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-new-target@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz" + integrity sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-nullish-coalescing-operator@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz" + integrity sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-numeric-separator@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz" + integrity sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-object-rest-spread@^7.27.2": + version "7.27.2" + resolved "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.2.tgz" + integrity sha512-AIUHD7xJ1mCrj3uPozvtngY3s0xpv7Nu7DoUSnzNY6Xam1Cy4rUznR//pvMHOhQ4AvbCexhbqXCtpxGHOGOO6g== + dependencies: + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/plugin-transform-destructuring" "^7.27.1" + "@babel/plugin-transform-parameters" "^7.27.1" + +"@babel/plugin-transform-object-super@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz" + integrity sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-replace-supers" "^7.27.1" + +"@babel/plugin-transform-optional-catch-binding@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz" + integrity sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-optional-chaining@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz" + integrity sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + +"@babel/plugin-transform-parameters@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.1.tgz" + integrity sha512-018KRk76HWKeZ5l4oTj2zPpSh+NbGdt0st5S6x0pga6HgrjBOJb24mMDHorFopOOd6YHkLgOZ+zaCjZGPO4aKg== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-private-methods@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz" + integrity sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-private-property-in-object@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz" + integrity sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.1" + "@babel/helper-create-class-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-property-literals@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz" + integrity sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-react-constant-elements@^7.21.3": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.27.1.tgz" + integrity sha512-edoidOjl/ZxvYo4lSBOQGDSyToYVkTAwyVoa2tkuYTSmjrB1+uAedoL5iROVLXkxH+vRgA7uP4tMg2pUJpZ3Ug== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-react-display-name@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.27.1.tgz" + integrity sha512-p9+Vl3yuHPmkirRrg021XiP+EETmPMQTLr6Ayjj85RLNEbb3Eya/4VI0vAdzQG9SEAl2Lnt7fy5lZyMzjYoZQQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-react-jsx-development@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz" + integrity sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q== + dependencies: + "@babel/plugin-transform-react-jsx" "^7.27.1" + +"@babel/plugin-transform-react-jsx@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz" + integrity sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.1" + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/plugin-syntax-jsx" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/plugin-transform-react-pure-annotations@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz" + integrity sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-regenerator@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.1.tgz" + integrity sha512-B19lbbL7PMrKr52BNPjCqg1IyNUIjTcxKj8uX9zHO+PmWN93s19NDr/f69mIkEp2x9nmDJ08a7lgHaTTzvW7mw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-regexp-modifiers@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz" + integrity sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-reserved-words@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz" + integrity sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-shorthand-properties@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz" + integrity sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-spread@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz" + integrity sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + +"@babel/plugin-transform-sticky-regex@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz" + integrity sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-template-literals@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz" + integrity sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-typeof-symbol@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz" + integrity sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-typescript@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.1.tgz" + integrity sha512-Q5sT5+O4QUebHdbwKedFBEwRLb02zJ7r4A5Gg2hUoLuU3FjdMcyqcywqUrLCaDsFCxzokf7u9kuy7qz51YUuAg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.1" + "@babel/helper-create-class-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + "@babel/plugin-syntax-typescript" "^7.27.1" + +"@babel/plugin-transform-unicode-escapes@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz" + integrity sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-unicode-property-regex@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz" + integrity sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-unicode-regex@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz" + integrity sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-unicode-sets-regex@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz" + integrity sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/preset-env@^7.11.0", "@babel/preset-env@^7.20.2": + version "7.27.2" + resolved "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.27.2.tgz" + integrity sha512-Ma4zSuYSlGNRlCLO+EAzLnCmJK2vdstgv+n7aUP+/IKZrOfWHOJVdSJtuub8RzHTj3ahD37k5OKJWvzf16TQyQ== + dependencies: + "@babel/compat-data" "^7.27.2" + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-validator-option" "^7.27.1" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.27.1" + "@babel/plugin-bugfix-safari-class-field-initializer-scope" "^7.27.1" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.27.1" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.27.1" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.27.1" + "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" + "@babel/plugin-syntax-import-assertions" "^7.27.1" + "@babel/plugin-syntax-import-attributes" "^7.27.1" + "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" + "@babel/plugin-transform-arrow-functions" "^7.27.1" + "@babel/plugin-transform-async-generator-functions" "^7.27.1" + "@babel/plugin-transform-async-to-generator" "^7.27.1" + "@babel/plugin-transform-block-scoped-functions" "^7.27.1" + "@babel/plugin-transform-block-scoping" "^7.27.1" + "@babel/plugin-transform-class-properties" "^7.27.1" + "@babel/plugin-transform-class-static-block" "^7.27.1" + "@babel/plugin-transform-classes" "^7.27.1" + "@babel/plugin-transform-computed-properties" "^7.27.1" + "@babel/plugin-transform-destructuring" "^7.27.1" + "@babel/plugin-transform-dotall-regex" "^7.27.1" + "@babel/plugin-transform-duplicate-keys" "^7.27.1" + "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.27.1" + "@babel/plugin-transform-dynamic-import" "^7.27.1" + "@babel/plugin-transform-exponentiation-operator" "^7.27.1" + "@babel/plugin-transform-export-namespace-from" "^7.27.1" + "@babel/plugin-transform-for-of" "^7.27.1" + "@babel/plugin-transform-function-name" "^7.27.1" + "@babel/plugin-transform-json-strings" "^7.27.1" + "@babel/plugin-transform-literals" "^7.27.1" + "@babel/plugin-transform-logical-assignment-operators" "^7.27.1" + "@babel/plugin-transform-member-expression-literals" "^7.27.1" + "@babel/plugin-transform-modules-amd" "^7.27.1" + "@babel/plugin-transform-modules-commonjs" "^7.27.1" + "@babel/plugin-transform-modules-systemjs" "^7.27.1" + "@babel/plugin-transform-modules-umd" "^7.27.1" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.27.1" + "@babel/plugin-transform-new-target" "^7.27.1" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.27.1" + "@babel/plugin-transform-numeric-separator" "^7.27.1" + "@babel/plugin-transform-object-rest-spread" "^7.27.2" + "@babel/plugin-transform-object-super" "^7.27.1" + "@babel/plugin-transform-optional-catch-binding" "^7.27.1" + "@babel/plugin-transform-optional-chaining" "^7.27.1" + "@babel/plugin-transform-parameters" "^7.27.1" + "@babel/plugin-transform-private-methods" "^7.27.1" + "@babel/plugin-transform-private-property-in-object" "^7.27.1" + "@babel/plugin-transform-property-literals" "^7.27.1" + "@babel/plugin-transform-regenerator" "^7.27.1" + "@babel/plugin-transform-regexp-modifiers" "^7.27.1" + "@babel/plugin-transform-reserved-words" "^7.27.1" + "@babel/plugin-transform-shorthand-properties" "^7.27.1" + "@babel/plugin-transform-spread" "^7.27.1" + "@babel/plugin-transform-sticky-regex" "^7.27.1" + "@babel/plugin-transform-template-literals" "^7.27.1" + "@babel/plugin-transform-typeof-symbol" "^7.27.1" + "@babel/plugin-transform-unicode-escapes" "^7.27.1" + "@babel/plugin-transform-unicode-property-regex" "^7.27.1" + "@babel/plugin-transform-unicode-regex" "^7.27.1" + "@babel/plugin-transform-unicode-sets-regex" "^7.27.1" + "@babel/preset-modules" "0.1.6-no-external-plugins" + babel-plugin-polyfill-corejs2 "^0.4.10" + babel-plugin-polyfill-corejs3 "^0.11.0" + babel-plugin-polyfill-regenerator "^0.6.1" + core-js-compat "^3.40.0" + semver "^6.3.1" + +"@babel/preset-modules@0.1.6-no-external-plugins": + version "0.1.6-no-external-plugins" + resolved "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz" + 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-react@^7.18.6": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.27.1.tgz" + integrity sha512-oJHWh2gLhU9dW9HHr42q0cI0/iHHXTLGe39qvpAZZzagHy0MzYLCnCVV0symeRvzmjHyVU7mw2K06E6u/JwbhA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-validator-option" "^7.27.1" + "@babel/plugin-transform-react-display-name" "^7.27.1" + "@babel/plugin-transform-react-jsx" "^7.27.1" + "@babel/plugin-transform-react-jsx-development" "^7.27.1" + "@babel/plugin-transform-react-pure-annotations" "^7.27.1" + +"@babel/preset-typescript@^7.21.0": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz" + integrity sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-validator-option" "^7.27.1" + "@babel/plugin-syntax-jsx" "^7.27.1" + "@babel/plugin-transform-modules-commonjs" "^7.27.1" + "@babel/plugin-transform-typescript" "^7.27.1" + +"@babel/runtime@^7.0.0", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.13", "@babel/runtime@^7.23.2", "@babel/runtime@^7.24.5", "@babel/runtime@^7.25.0", "@babel/runtime@^7.27.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz" + integrity sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog== + +"@babel/runtime@^7.26.10": + version "7.27.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.6.tgz#ec4070a04d76bae8ddbb10770ba55714a417b7c6" + integrity sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q== + +"@babel/template@^7.27.1", "@babel/template@^7.3.3": + version "7.27.2" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz" + integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/parser" "^7.27.2" + "@babel/types" "^7.27.1" + +"@babel/traverse@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz" + integrity sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.27.1" + "@babel/parser" "^7.27.1" + "@babel/template" "^7.27.1" + "@babel/types" "^7.27.1" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.21.3", "@babel/types@^7.27.1", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz" + integrity sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@csstools/css-parser-algorithms@^3.0.4": + version "3.0.4" + resolved "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz" + integrity sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A== + +"@csstools/css-tokenizer@^3.0.3": + version "3.0.3" + resolved "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz" + integrity sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw== + +"@csstools/media-query-list-parser@^4.0.2": + version "4.0.2" + resolved "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.2.tgz" + integrity sha512-EUos465uvVvMJehckATTlNqGj4UJWkTmdWuDMjqvSUkjGpmOyFZBVwb4knxCm/k2GMTXY+c/5RkdndzFYWeX5A== + +"@csstools/selector-specificity@^5.0.0": + version "5.0.0" + resolved "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz" + integrity sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw== + +"@dnd-kit/accessibility@^3.1.1": + version "3.1.1" + resolved "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz" + integrity sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw== + dependencies: + tslib "^2.0.0" + +"@dnd-kit/core@6.3.1": + version "6.3.1" + resolved "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz" + integrity sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ== + dependencies: + "@dnd-kit/accessibility" "^3.1.1" + "@dnd-kit/utilities" "^3.2.2" + tslib "^2.0.0" + +"@dnd-kit/modifiers@9.0.0": + version "9.0.0" + resolved "https://registry.npmjs.org/@dnd-kit/modifiers/-/modifiers-9.0.0.tgz" + integrity sha512-ybiLc66qRGuZoC20wdSSG6pDXFikui/dCNGthxv4Ndy8ylErY0N3KVxY2bgo7AWwIbxDmXDg3ylAFmnrjcbVvw== + dependencies: + "@dnd-kit/utilities" "^3.2.2" + tslib "^2.0.0" + +"@dnd-kit/sortable@10.0.0": + version "10.0.0" + resolved "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz" + integrity sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg== + dependencies: + "@dnd-kit/utilities" "^3.2.2" + tslib "^2.0.0" + +"@dnd-kit/utilities@^3.2.2": + version "3.2.2" + resolved "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz" + integrity sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg== + dependencies: + tslib "^2.0.0" + +"@dual-bundle/import-meta-resolve@^4.1.0": + version "4.1.0" + resolved "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz" + integrity sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg== + +"@emnapi/core@^1.4.3": + version "1.4.3" + resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.4.3.tgz#9ac52d2d5aea958f67e52c40a065f51de59b77d6" + integrity sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g== + dependencies: + "@emnapi/wasi-threads" "1.0.2" + tslib "^2.4.0" + +"@emnapi/runtime@^1.4.0", "@emnapi/runtime@^1.4.3": + version "1.4.3" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.4.3.tgz#c0564665c80dc81c448adac23f9dfbed6c838f7d" + integrity sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ== + dependencies: + tslib "^2.4.0" + +"@emnapi/wasi-threads@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz#977f44f844eac7d6c138a415a123818c655f874c" + integrity sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA== + dependencies: + tslib "^2.4.0" + +"@emoji-mart/data@1.2.1": + version "1.2.1" + resolved "https://registry.npmjs.org/@emoji-mart/data/-/data-1.2.1.tgz" + integrity sha512-no2pQMWiBy6gpBEiqGeU77/bFejDqUTRY7KX+0+iur13op3bqUsXdnwoZs6Xb1zbv0gAj5VvS1PWoUUckSr5Dw== + +"@emoji-mart/react@1.1.1": + version "1.1.1" + resolved "https://registry.npmjs.org/@emoji-mart/react/-/react-1.1.1.tgz" + integrity sha512-NMlFNeWgv1//uPsvLxvGQoIerPuVdXwK/EUek8OOkJ6wVOWPUizRBJU0hDqWZCOROVpfBgCemaC3m6jDOXi03g== + +"@emotion/babel-plugin@^11.13.5": + version "11.13.5" + resolved "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz" + integrity sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ== + dependencies: + "@babel/helper-module-imports" "^7.16.7" + "@babel/runtime" "^7.18.3" + "@emotion/hash" "^0.9.2" + "@emotion/memoize" "^0.9.0" + "@emotion/serialize" "^1.3.3" + babel-plugin-macros "^3.1.0" + convert-source-map "^1.5.0" + escape-string-regexp "^4.0.0" + find-root "^1.1.0" + source-map "^0.5.7" + stylis "4.2.0" + +"@emotion/cache@^11.14.0", "@emotion/cache@^11.4.0": + version "11.14.0" + resolved "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz" + integrity sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA== + dependencies: + "@emotion/memoize" "^0.9.0" + "@emotion/sheet" "^1.4.0" + "@emotion/utils" "^1.4.2" + "@emotion/weak-memoize" "^0.4.0" + stylis "4.2.0" + +"@emotion/hash@^0.9.2": + version "0.9.2" + resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz" + integrity sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g== + +"@emotion/is-prop-valid@1.2.2": + version "1.2.2" + resolved "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz" + integrity sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw== + dependencies: + "@emotion/memoize" "^0.8.1" + +"@emotion/memoize@^0.8.1": + version "0.8.1" + resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz" + integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== + +"@emotion/memoize@^0.9.0": + version "0.9.0" + resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz" + integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ== + +"@emotion/react@^11.8.1": + version "11.14.0" + resolved "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz" + integrity sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA== + dependencies: + "@babel/runtime" "^7.18.3" + "@emotion/babel-plugin" "^11.13.5" + "@emotion/cache" "^11.14.0" + "@emotion/serialize" "^1.3.3" + "@emotion/use-insertion-effect-with-fallbacks" "^1.2.0" + "@emotion/utils" "^1.4.2" + "@emotion/weak-memoize" "^0.4.0" + hoist-non-react-statics "^3.3.1" + +"@emotion/serialize@^1.3.3": + version "1.3.3" + resolved "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz" + integrity sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA== + dependencies: + "@emotion/hash" "^0.9.2" + "@emotion/memoize" "^0.9.0" + "@emotion/unitless" "^0.10.0" + "@emotion/utils" "^1.4.2" + csstype "^3.0.2" + +"@emotion/sheet@^1.4.0": + version "1.4.0" + resolved "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz" + integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg== + +"@emotion/unitless@0.8.1": + version "0.8.1" + resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz" + integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ== + +"@emotion/unitless@^0.10.0": + version "0.10.0" + resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz" + integrity sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg== + +"@emotion/use-insertion-effect-with-fallbacks@^1.2.0": + version "1.2.0" + resolved "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz" + integrity sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg== + +"@emotion/utils@^1.4.2": + version "1.4.2" + resolved "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz" + integrity sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA== + +"@emotion/weak-memoize@^0.4.0": + version "0.4.0" + resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz" + integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg== + +"@esbuild/aix-ppc64@0.25.5": + version "0.25.5" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz#4e0f91776c2b340e75558f60552195f6fad09f18" + integrity sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA== + +"@esbuild/android-arm64@0.25.5": + version "0.25.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz#bc766407f1718923f6b8079c8c61bf86ac3a6a4f" + integrity sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg== + +"@esbuild/android-arm@0.25.5": + version "0.25.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.5.tgz#4290d6d3407bae3883ad2cded1081a234473ce26" + integrity sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA== + +"@esbuild/android-x64@0.25.5": + version "0.25.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.5.tgz#40c11d9cbca4f2406548c8a9895d321bc3b35eff" + integrity sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw== + +"@esbuild/darwin-arm64@0.25.5": + version "0.25.5" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz#49d8bf8b1df95f759ac81eb1d0736018006d7e34" + integrity sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ== + +"@esbuild/darwin-x64@0.25.5": + version "0.25.5" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz#e27a5d92a14886ef1d492fd50fc61a2d4d87e418" + integrity sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ== + +"@esbuild/freebsd-arm64@0.25.5": + version "0.25.5" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz#97cede59d638840ca104e605cdb9f1b118ba0b1c" + integrity sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw== + +"@esbuild/freebsd-x64@0.25.5": + version "0.25.5" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz#71c77812042a1a8190c3d581e140d15b876b9c6f" + integrity sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw== + +"@esbuild/linux-arm64@0.25.5": + version "0.25.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz#f7b7c8f97eff8ffd2e47f6c67eb5c9765f2181b8" + integrity sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg== + +"@esbuild/linux-arm@0.25.5": + version "0.25.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz#2a0be71b6cd8201fa559aea45598dffabc05d911" + integrity sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw== + +"@esbuild/linux-ia32@0.25.5": + version "0.25.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz#763414463cd9ea6fa1f96555d2762f9f84c61783" + integrity sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA== + +"@esbuild/linux-loong64@0.25.5": + version "0.25.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz#428cf2213ff786a502a52c96cf29d1fcf1eb8506" + integrity sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg== + +"@esbuild/linux-mips64el@0.25.5": + version "0.25.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz#5cbcc7fd841b4cd53358afd33527cd394e325d96" + integrity sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg== + +"@esbuild/linux-ppc64@0.25.5": + version "0.25.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz#0d954ab39ce4f5e50f00c4f8c4fd38f976c13ad9" + integrity sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ== + +"@esbuild/linux-riscv64@0.25.5": + version "0.25.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz#0e7dd30730505abd8088321e8497e94b547bfb1e" + integrity sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA== + +"@esbuild/linux-s390x@0.25.5": + version "0.25.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz#5669af81327a398a336d7e40e320b5bbd6e6e72d" + integrity sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ== + +"@esbuild/linux-x64@0.25.5": + version "0.25.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz#b2357dd153aa49038967ddc1ffd90c68a9d2a0d4" + integrity sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw== + +"@esbuild/netbsd-arm64@0.25.5": + version "0.25.5" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz#53b4dfb8fe1cee93777c9e366893bd3daa6ba63d" + integrity sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw== + +"@esbuild/netbsd-x64@0.25.5": + version "0.25.5" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz#a0206f6314ce7dc8713b7732703d0f58de1d1e79" + integrity sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ== + +"@esbuild/openbsd-arm64@0.25.5": + version "0.25.5" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz#2a796c87c44e8de78001d808c77d948a21ec22fd" + integrity sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw== + +"@esbuild/openbsd-x64@0.25.5": + version "0.25.5" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz#28d0cd8909b7fa3953af998f2b2ed34f576728f0" + integrity sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg== + +"@esbuild/sunos-x64@0.25.5": + version "0.25.5" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz#a28164f5b997e8247d407e36c90d3fd5ddbe0dc5" + integrity sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA== + +"@esbuild/win32-arm64@0.25.5": + version "0.25.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz#6eadbead38e8bd12f633a5190e45eff80e24007e" + integrity sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw== + +"@esbuild/win32-ia32@0.25.5": + version "0.25.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz#bab6288005482f9ed2adb9ded7e88eba9a62cc0d" + integrity sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ== + +"@esbuild/win32-x64@0.25.5": + version "0.25.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz#7fc114af5f6563f19f73324b5d5ff36ece0803d1" + integrity sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g== + +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.7.0": + version "4.7.0" + resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz" + integrity sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw== + dependencies: + eslint-visitor-keys "^3.4.3" + +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1": + version "4.12.1" + resolved "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz" + integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.57.0": + version "8.57.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" + integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== + +"@floating-ui/core@^1.7.0": + version "1.7.0" + resolved "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.0.tgz" + integrity sha512-FRdBLykrPPA6P76GGGqlex/e7fbe0F1ykgxHYNXQsH/iTEtjMj/f9bpY5oQqbjt5VgZvgz/uKXbGuROijh3VLA== + dependencies: + "@floating-ui/utils" "^0.2.9" + +"@floating-ui/dom@^1.0.1": + version "1.7.0" + resolved "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.0.tgz" + integrity sha512-lGTor4VlXcesUMh1cupTUTDoCxMb0V6bm3CnxHzQcw8Eaf1jQbgQX4i02fYgT0vJ82tb5MZ4CZk1LRGkktJCzg== + dependencies: + "@floating-ui/core" "^1.7.0" + "@floating-ui/utils" "^0.2.9" + +"@floating-ui/utils@^0.2.9": + version "0.2.9" + resolved "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz" + integrity sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg== + +"@fontsource-variable/roboto-flex@5.1.1": + version "5.1.1" + resolved "https://registry.npmjs.org/@fontsource-variable/roboto-flex/-/roboto-flex-5.1.1.tgz" + integrity sha512-cMtqtR+EhqTcox5+Sz+/+N7C8hHheQm1EtxJ9qrr356N2lwbpEU0y5OA4as+V7cdiaEFxXw6jzO9KoqLMZ6tNg== + +"@fontsource-variable/roboto-flex@5.2.5": + version "5.2.5" + resolved "https://registry.npmjs.org/@fontsource-variable/roboto-flex/-/roboto-flex-5.2.5.tgz" + integrity sha512-yrZ9rWNvfM4IBqJSpoFV4wC1GaKNwpzhihyBke+N3WiA0Y7LYxwj6kvvHgecabVIHFL3ZZJTwj3KcKOCyASFPg== + +"@fontsource/material-icons-outlined@5.1.1": + version "5.1.1" + resolved "https://registry.npmjs.org/@fontsource/material-icons-outlined/-/material-icons-outlined-5.1.1.tgz" + integrity sha512-HjWe3anHu9RptoAvm2UF6MPXkwsbFbH+vkg48GRIhXPsXyfmZteGHVFZpPxaZnakrmnLXDxUnnKSj7zzqk+Rhg== + +"@fontsource/material-icons-outlined@5.2.5": + version "5.2.5" + resolved "https://registry.npmjs.org/@fontsource/material-icons-outlined/-/material-icons-outlined-5.2.5.tgz" + integrity sha512-soAUWorSKrLN0a7wk74pedV0ZxhLaMD40DuUTMIqTpDcdJ/pxaG+/OcXtMEzx2+5K5FwL7yS525ZInAzPG051Q== + +"@fontsource/material-icons@5.2.5": + version "5.2.5" + resolved "https://registry.npmjs.org/@fontsource/material-icons/-/material-icons-5.2.5.tgz" + integrity sha512-9k0LBRVgResIeD+vC/epYmm/awN2k2L8twwEtUWQ3FHluMi+7PbISOpXqksvfqPn9FJy4/KEeWOhFTR/SrbhNw== + +"@formatjs/ecma402-abstract@2.3.4": + version "2.3.4" + resolved "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.4.tgz" + integrity sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA== + dependencies: + "@formatjs/fast-memoize" "2.2.7" + "@formatjs/intl-localematcher" "0.6.1" + decimal.js "^10.4.3" + tslib "^2.8.0" + +"@formatjs/fast-memoize@2.2.7": + version "2.2.7" + resolved "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz" + integrity sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ== + dependencies: + tslib "^2.8.0" + +"@formatjs/icu-messageformat-parser@2.11.2": + version "2.11.2" + resolved "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.2.tgz" + integrity sha512-AfiMi5NOSo2TQImsYAg8UYddsNJ/vUEv/HaNqiFjnI3ZFfWihUtD5QtuX6kHl8+H+d3qvnE/3HZrfzgdWpsLNA== + dependencies: + "@formatjs/ecma402-abstract" "2.3.4" + "@formatjs/icu-skeleton-parser" "1.8.14" + tslib "^2.8.0" + +"@formatjs/icu-skeleton-parser@1.8.14": + version "1.8.14" + resolved "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.14.tgz" + integrity sha512-i4q4V4qslThK4Ig8SxyD76cp3+QJ3sAqr7f6q9VVfeGtxG9OhiAk3y9XF6Q41OymsKzsGQ6OQQoJNY4/lI8TcQ== + dependencies: + "@formatjs/ecma402-abstract" "2.3.4" + tslib "^2.8.0" + +"@formatjs/intl-localematcher@0.6.1": + version "0.6.1" + resolved "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.6.1.tgz" + integrity sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg== + dependencies: + tslib "^2.8.0" + +"@gouvfr-lasuite/integration@1.0.2": + version "1.0.2" + resolved "https://registry.npmjs.org/@gouvfr-lasuite/integration/-/integration-1.0.2.tgz" + integrity sha512-npOotZQSyu6SffHiPP+jQVOkJ3qW2KE2cANhEK92sNLX9uZqQaCqljO5GhzsBmh0lB76fiXnrr9i8SIpnDUSZg== + +"@gouvfr-lasuite/integration@1.0.3": + version "1.0.3" + resolved "https://registry.npmjs.org/@gouvfr-lasuite/integration/-/integration-1.0.3.tgz" + integrity sha512-OgP28CqlPi35wQPul1Dr52SngACXAk8buLGqHYXDp23fbTOJThqarrZE/pgJHoc9Ndwiu7ngwBSO4rZ7OPyMpA== + +"@gouvfr-lasuite/ui-kit@0.7.0": + version "0.7.0" + resolved "https://registry.npmjs.org/@gouvfr-lasuite/ui-kit/-/ui-kit-0.7.0.tgz" + integrity sha512-vDdhnyR//uq3k6WpQb1c0kD8yHbf0GFIwmuX4hroXj3RxqW7jcKnikG3yg91YztggZQqrhFtJWGjXrNFaSRg2g== + dependencies: + "@dnd-kit/core" "6.3.1" + "@dnd-kit/modifiers" "9.0.0" + "@dnd-kit/sortable" "10.0.0" + "@gouvfr-lasuite/integration" "1.0.2" + "@openfun/cunningham-react" "3.0.0" + "@types/node" "22.10.7" + clsx "2.1.1" + cmdk "1.0.4" + react "19.0.0" + react-arborist "3.4.3" + react-aria-components "1.8.0" + react-dom "19.0.0" + react-resizable-panels "2.1.7" + react-stately "3.37.0" + +"@gulpjs/to-absolute-glob@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@gulpjs/to-absolute-glob/-/to-absolute-glob-4.0.0.tgz#1fc2460d3953e1d9b9f2dfdb4bcc99da4710c021" + integrity sha512-kjotm7XJrJ6v+7knhPaRgaT6q8F8K2jiafwYdNHLzmV0uGLuZY43FK6smNSHUPrhq5kX2slCUy+RGG/xGqmIKA== + dependencies: + is-negated-glob "^1.0.0" + +"@humanwhocodes/config-array@^0.11.14": + version "0.11.14" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" + integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== + dependencies: + "@humanwhocodes/object-schema" "^2.0.2" + debug "^4.3.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== + +"@img/sharp-darwin-arm64@0.34.1": + version "0.34.1" + resolved "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.1.tgz" + integrity sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A== + optionalDependencies: + "@img/sharp-libvips-darwin-arm64" "1.1.0" + +"@img/sharp-darwin-x64@0.34.1": + version "0.34.1" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.1.tgz#f1f1d386719f6933796415d84937502b7199a744" + integrity sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q== + optionalDependencies: + "@img/sharp-libvips-darwin-x64" "1.1.0" + +"@img/sharp-libvips-darwin-arm64@1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.1.0.tgz" + integrity sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA== + +"@img/sharp-libvips-darwin-x64@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.1.0.tgz#1239c24426c06a8e833815562f78047a3bfbaaf8" + integrity sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ== + +"@img/sharp-libvips-linux-arm64@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.1.0.tgz#20d276cefd903ee483f0441ba35961679c286315" + integrity sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew== + +"@img/sharp-libvips-linux-arm@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.1.0.tgz#067c0b566eae8063738cf1b1db8f8a8573b5465c" + integrity sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA== + +"@img/sharp-libvips-linux-ppc64@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.1.0.tgz#682334595f2ca00e0a07a675ba170af165162802" + integrity sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ== + +"@img/sharp-libvips-linux-s390x@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.1.0.tgz#82fcd68444b3666384235279c145c2b28d8ee302" + integrity sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA== + +"@img/sharp-libvips-linux-x64@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.1.0.tgz#65b2b908bf47156b0724fde9095676c83a18cf5a" + integrity sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q== + +"@img/sharp-libvips-linuxmusl-arm64@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.1.0.tgz#72accf924e80b081c8db83b900b444a67c203f01" + integrity sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w== + +"@img/sharp-libvips-linuxmusl-x64@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.1.0.tgz#1fa052737e203f46bf44192acd01f9faf11522d7" + integrity sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A== + +"@img/sharp-linux-arm64@0.34.1": + version "0.34.1" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.1.tgz#c36ef964499b8cfc2d2ed88fe68f27ce41522c80" + integrity sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ== + optionalDependencies: + "@img/sharp-libvips-linux-arm64" "1.1.0" + +"@img/sharp-linux-arm@0.34.1": + version "0.34.1" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.1.tgz#c96e38ff028d645912bb0aa132a7178b96997866" + integrity sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA== + optionalDependencies: + "@img/sharp-libvips-linux-arm" "1.1.0" + +"@img/sharp-linux-s390x@0.34.1": + version "0.34.1" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.1.tgz#8ac58d9a49dcb08215e76c8d450717979b7815c3" + integrity sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA== + optionalDependencies: + "@img/sharp-libvips-linux-s390x" "1.1.0" + +"@img/sharp-linux-x64@0.34.1": + version "0.34.1" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.1.tgz#3d8652efac635f0dba39d5e3b8b49515a2b2dee1" + integrity sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA== + optionalDependencies: + "@img/sharp-libvips-linux-x64" "1.1.0" + +"@img/sharp-linuxmusl-arm64@0.34.1": + version "0.34.1" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.1.tgz#b267e6a3e06f9e4d345cde471e5480c5c39e6969" + integrity sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-arm64" "1.1.0" + +"@img/sharp-linuxmusl-x64@0.34.1": + version "0.34.1" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.1.tgz#a8dee4b6227f348c4bbacaa6ac3dc584a1a80391" + integrity sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-x64" "1.1.0" + +"@img/sharp-wasm32@0.34.1": + version "0.34.1" + resolved "https://registry.yarnpkg.com/@img/sharp-wasm32/-/sharp-wasm32-0.34.1.tgz#f7dfd66b6c231269042d3d8750c90f28b9ddcba1" + integrity sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg== + dependencies: + "@emnapi/runtime" "^1.4.0" + +"@img/sharp-win32-ia32@0.34.1": + version "0.34.1" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.1.tgz#4bc293705df76a5f0a02df66ca3dc12e88f61332" + integrity sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw== + +"@img/sharp-win32-x64@0.34.1": + version "0.34.1" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.1.tgz#8a7922fec949f037c204c79f6b83238d2482384b" + integrity sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw== + +"@internationalized/date@3.6.0", "@internationalized/date@^3.6.0": + version "3.6.0" + resolved "https://registry.npmjs.org/@internationalized/date/-/date-3.6.0.tgz" + integrity sha512-+z6ti+CcJnRlLHok/emGEsWQhe7kfSmEW+/6qCzvKY67YPh7YOBfvc7+/+NXq+zJlbArg30tYpqLjNgcAYv2YQ== + dependencies: + "@swc/helpers" "^0.5.0" + +"@internationalized/date@3.8.0", "@internationalized/date@^3.8.0": + version "3.8.0" + resolved "https://registry.npmjs.org/@internationalized/date/-/date-3.8.0.tgz" + integrity sha512-J51AJ0fEL68hE4CwGPa6E0PO6JDaVLd8aln48xFCSy7CZkZc96dGEGmLs2OEEbBxcsVZtfrqkXJwI2/MSG8yKw== + dependencies: + "@swc/helpers" "^0.5.0" + +"@internationalized/date@^3.8.1": + version "3.8.1" + resolved "https://registry.npmjs.org/@internationalized/date/-/date-3.8.1.tgz" + integrity sha512-PgVE6B6eIZtzf9Gu5HvJxRK3ufUFz9DhspELuhW/N0GuMGMTLvPQNRkHP2hTuP9lblOk+f+1xi96sPiPXANXAA== + dependencies: + "@swc/helpers" "^0.5.0" + +"@internationalized/message@^3.1.6", "@internationalized/message@^3.1.7": + version "3.1.7" + resolved "https://registry.npmjs.org/@internationalized/message/-/message-3.1.7.tgz" + integrity sha512-gLQlhEW4iO7DEFPf/U7IrIdA3UyLGS0opeqouaFwlMObLUzwexRjbygONHDVbC9G9oFLXsLyGKYkJwqXw/QADg== + dependencies: + "@swc/helpers" "^0.5.0" + intl-messageformat "^10.1.0" + +"@internationalized/number@^3.6.0", "@internationalized/number@^3.6.1", "@internationalized/number@^3.6.2": + version "3.6.2" + resolved "https://registry.npmjs.org/@internationalized/number/-/number-3.6.2.tgz" + integrity sha512-E5QTOlMg9wo5OrKdHD6edo1JJlIoOsylh0+mbf0evi1tHJwMZfJSaBpGtnJV9N7w3jeiioox9EG/EWRWPh82vg== + dependencies: + "@swc/helpers" "^0.5.0" + +"@internationalized/string@^3.2.5", "@internationalized/string@^3.2.6": + version "3.2.6" + resolved "https://registry.npmjs.org/@internationalized/string/-/string-3.2.6.tgz" + integrity sha512-LR2lnM4urJta5/wYJVV7m8qk5DrMZmLRTuFhbQO5b9/sKLHgty6unQy1Li4+Su2DWydmB4aZdS5uxBRXIq2aAw== + dependencies: + "@swc/helpers" "^0.5.0" + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": + version "0.1.3" + resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz" + integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + +"@jest/core@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz" + integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== + dependencies: + "@jest/console" "^29.7.0" + "@jest/reporters" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-resolve-dependencies "^29.7.0" + jest-runner "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + jest-watcher "^29.7.0" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== + dependencies: + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== + dependencies: + jest-get-type "^29.6.3" + +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== + dependencies: + expect "^29.7.0" + jest-snapshot "^29.7.0" + +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== + dependencies: + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +"@jest/globals@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" + +"@jest/reporters@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz" + integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^6.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz" + integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== + dependencies: + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz" + integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== + dependencies: + "@jest/test-result" "^29.7.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + slash "^3.0.0" + +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.8" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz" + integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/source-map@^0.3.3": + version "0.3.6" + resolved "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz" + integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15", "@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.0" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@keyv/serialize@^1.0.3": + version "1.0.3" + resolved "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.0.3.tgz" + integrity sha512-qnEovoOp5Np2JDGonIDL6Ayihw0RhnRh6vxPuHo4RDn1UOzwEo4AeIfpL6UGIrsceWrCMiVPgwRjbHu4vYFc3g== + dependencies: + buffer "^6.0.3" + +"@napi-rs/wasm-runtime@^0.2.9": + version "0.2.11" + resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.11.tgz#192c1610e1625048089ab4e35bc0649ce478500e" + integrity sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA== + dependencies: + "@emnapi/core" "^1.4.3" + "@emnapi/runtime" "^1.4.3" + "@tybys/wasm-util" "^0.9.0" + +"@next/env@15.3.3": + version "15.3.3" + resolved "https://registry.npmjs.org/@next/env/-/env-15.3.3.tgz" + integrity sha512-OdiMrzCl2Xi0VTjiQQUK0Xh7bJHnOuET2s+3V+Y40WJBAXrJeGA3f+I8MZJ/YQ3mVGi5XGR1L66oFlgqXhQ4Vw== + +"@next/eslint-plugin-next@15.3.3": + version "15.3.3" + resolved "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.3.3.tgz" + integrity sha512-VKZJEiEdpKkfBmcokGjHu0vGDG+8CehGs90tBEy/IDoDDKGngeyIStt2MmE5FYNyU9BhgR7tybNWTAJY/30u+Q== + dependencies: + fast-glob "3.3.1" + +"@next/swc-darwin-arm64@15.3.3": + version "15.3.3" + resolved "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.3.3.tgz" + integrity sha512-WRJERLuH+O3oYB4yZNVahSVFmtxRNjNF1I1c34tYMoJb0Pve+7/RaLAJJizyYiFhjYNGHRAE1Ri2Fd23zgDqhg== + +"@next/swc-darwin-x64@15.3.3": + version "15.3.3" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-15.3.3.tgz#71588bad245180ffd1af1e1f894477287e739eb0" + integrity sha512-XHdzH/yBc55lu78k/XwtuFR/ZXUTcflpRXcsu0nKmF45U96jt1tsOZhVrn5YH+paw66zOANpOnFQ9i6/j+UYvw== + +"@next/swc-linux-arm64-gnu@15.3.3": + version "15.3.3" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.3.3.tgz#66a15f749c14f04a89f8c7e21c7a8d343fc34e6e" + integrity sha512-VZ3sYL2LXB8znNGcjhocikEkag/8xiLgnvQts41tq6i+wql63SMS1Q6N8RVXHw5pEUjiof+II3HkDd7GFcgkzw== + +"@next/swc-linux-arm64-musl@15.3.3": + version "15.3.3" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.3.3.tgz#14bd66213f7f33d6909574750bcb05037221a2ac" + integrity sha512-h6Y1fLU4RWAp1HPNJWDYBQ+e3G7sLckyBXhmH9ajn8l/RSMnhbuPBV/fXmy3muMcVwoJdHL+UtzRzs0nXOf9SA== + +"@next/swc-linux-x64-gnu@15.3.3": + version "15.3.3" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.3.tgz#4a19434545e5e752d9a3ed71f9b34982725f6293" + integrity sha512-jJ8HRiF3N8Zw6hGlytCj5BiHyG/K+fnTKVDEKvUCyiQ/0r5tgwO7OgaRiOjjRoIx2vwLR+Rz8hQoPrnmFbJdfw== + +"@next/swc-linux-x64-musl@15.3.3": + version "15.3.3" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.3.tgz#41ab140dd0a04ab7291adbec5836c1ce251a588c" + integrity sha512-HrUcTr4N+RgiiGn3jjeT6Oo208UT/7BuTr7K0mdKRBtTbT4v9zJqCDKO97DUqqoBK1qyzP1RwvrWTvU6EPh/Cw== + +"@next/swc-win32-arm64-msvc@15.3.3": + version "15.3.3" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.3.tgz#fcd1d7e0007b7b73d1acdbf0ad6d91f7aa2deb15" + integrity sha512-SxorONgi6K7ZUysMtRF3mIeHC5aA3IQLmKFQzU0OuhuUYwpOBc1ypaLJLP5Bf3M9k53KUUUj4vTPwzGvl/NwlQ== + +"@next/swc-win32-x64-msvc@15.3.3": + version "15.3.3" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.3.tgz#c0e33e069d7922dd0546cac77a0247ad81d4a1aa" + integrity sha512-4QZG6F8enl9/S2+yIiOiju0iCTFd93d8VC1q9LZS4p/Xuk81W2QDjCFeoogmrWWkAD59z8ZxepBQap2dKS5ruw== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" + 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.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@nolyfill/is-core-module@1.0.39": + version "1.0.39" + resolved "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz" + integrity sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA== + +"@openfun/cunningham-react@3.0.0": + version "3.0.0" + resolved "https://registry.npmjs.org/@openfun/cunningham-react/-/cunningham-react-3.0.0.tgz" + integrity sha512-9LhcqiAkCriJbQouc3wiIJ1Ujd8Q9uvo0mubw21yZTm5d6TLj/XNzzX0mwWaoVSe2szHtcR0vjpRqls2Ue7u4g== + dependencies: + "@fontsource-variable/roboto-flex" "5.1.1" + "@fontsource/material-icons-outlined" "5.1.1" + "@internationalized/date" "3.6.0" + "@openfun/cunningham-tokens" "*" + "@react-aria/calendar" "3.6.0" + "@react-aria/datepicker" "3.12.0" + "@react-aria/i18n" "3.12.4" + "@react-stately/calendar" "3.6.0" + "@react-stately/datepicker" "3.11.0" + "@tanstack/react-table" "8.20.6" + "@types/react-modal" "3.16.3" + chromatic "11.22.0" + classnames "2.5.1" + downshift "9.0.8" + react "19.0.0" + react-aria "3.36.0" + react-aria-components "1.5.0" + react-dom "19.0.0" + react-modal "3.16.3" + react-stately "3.34.0" + +"@openfun/cunningham-react@3.1.0": + version "3.1.0" + resolved "https://registry.npmjs.org/@openfun/cunningham-react/-/cunningham-react-3.1.0.tgz" + integrity sha512-gTTRr4mQqMhN2DdxQeukUXHNrJ8kFY4b4+HA10lH9GBT73lbIVP66SG1uSqhBAGlXGmMSKEk8uUHOKgBKa9xEQ== + dependencies: + "@fontsource-variable/roboto-flex" "5.2.5" + "@fontsource/material-icons-outlined" "5.2.5" + "@internationalized/date" "3.8.0" + "@openfun/cunningham-tokens" "*" + "@react-aria/calendar" "3.8.0" + "@react-aria/datepicker" "3.14.2" + "@react-aria/i18n" "3.12.8" + "@react-stately/calendar" "3.8.0" + "@react-stately/datepicker" "3.14.0" + "@tanstack/react-table" "8.21.3" + "@types/react-modal" "3.16.3" + chromatic "11.28.2" + classnames "2.5.1" + downshift "9.0.9" + react-aria "3.39.0" + react-aria-components "1.8.0" + react-modal "3.16.3" + react-stately "3.37.0" + +"@openfun/cunningham-tokens@*": + version "2.2.0" + resolved "https://registry.npmjs.org/@openfun/cunningham-tokens/-/cunningham-tokens-2.2.0.tgz" + integrity sha512-ZLMbb5LTqGIJarNjs6/qB/Ga1jtyvJGoOcCqgb/X1UltkmM1A0JMERFv9fwIausuvwSdfAxQgawPav0F5SlUTw== + dependencies: + chalk "4.1.2" + commander "13.0.0" + deepmerge "4.3.1" + figlet "1.8.0" + ts-node "10.9.2" + +"@opentelemetry/api-logs@0.57.2": + version "0.57.2" + resolved "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.57.2.tgz" + integrity sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A== + dependencies: + "@opentelemetry/api" "^1.3.0" + +"@opentelemetry/api@^1.3.0", "@opentelemetry/api@^1.9.0": + version "1.9.0" + resolved "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz" + integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg== + +"@opentelemetry/context-async-hooks@^1.30.1": + version "1.30.1" + resolved "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.30.1.tgz" + integrity sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA== + +"@opentelemetry/core@1.30.1", "@opentelemetry/core@^1.1.0", "@opentelemetry/core@^1.26.0", "@opentelemetry/core@^1.30.1", "@opentelemetry/core@^1.8.0": + version "1.30.1" + resolved "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz" + integrity sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ== + dependencies: + "@opentelemetry/semantic-conventions" "1.28.0" + +"@opentelemetry/instrumentation-amqplib@^0.46.1": + version "0.46.1" + resolved "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.46.1.tgz" + integrity sha512-AyXVnlCf/xV3K/rNumzKxZqsULyITJH6OVLiW6730JPRqWA7Zc9bvYoVNpN6iOpTU8CasH34SU/ksVJmObFibQ== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-connect@0.43.1": + version "0.43.1" + resolved "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.43.1.tgz" + integrity sha512-ht7YGWQuV5BopMcw5Q2hXn3I8eG8TH0J/kc/GMcW4CuNTgiP6wCu44BOnucJWL3CmFWaRHI//vWyAhaC8BwePw== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/semantic-conventions" "^1.27.0" + "@types/connect" "3.4.38" + +"@opentelemetry/instrumentation-dataloader@0.16.1": + version "0.16.1" + resolved "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.16.1.tgz" + integrity sha512-K/qU4CjnzOpNkkKO4DfCLSQshejRNAJtd4esgigo/50nxCB6XCyi1dhAblUHM9jG5dRm8eu0FB+t87nIo99LYQ== + dependencies: + "@opentelemetry/instrumentation" "^0.57.1" + +"@opentelemetry/instrumentation-express@0.47.1": + version "0.47.1" + resolved "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.47.1.tgz" + integrity sha512-QNXPTWteDclR2B4pDFpz0TNghgB33UMjUt14B+BZPmtH1MwUFAfLHBaP5If0Z5NZC+jaH8oF2glgYjrmhZWmSw== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-fs@0.19.1": + version "0.19.1" + resolved "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.19.1.tgz" + integrity sha512-6g0FhB3B9UobAR60BGTcXg4IHZ6aaYJzp0Ki5FhnxyAPt8Ns+9SSvgcrnsN2eGmk3RWG5vYycUGOEApycQL24A== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.57.1" + +"@opentelemetry/instrumentation-generic-pool@0.43.1": + version "0.43.1" + resolved "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.43.1.tgz" + integrity sha512-M6qGYsp1cURtvVLGDrPPZemMFEbuMmCXgQYTReC/IbimV5sGrLBjB+/hANUpRZjX67nGLdKSVLZuQQAiNz+sww== + dependencies: + "@opentelemetry/instrumentation" "^0.57.1" + +"@opentelemetry/instrumentation-graphql@0.47.1": + version "0.47.1" + resolved "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.47.1.tgz" + integrity sha512-EGQRWMGqwiuVma8ZLAZnExQ7sBvbOx0N/AE/nlafISPs8S+QtXX+Viy6dcQwVWwYHQPAcuY3bFt3xgoAwb4ZNQ== + dependencies: + "@opentelemetry/instrumentation" "^0.57.1" + +"@opentelemetry/instrumentation-hapi@0.45.2": + version "0.45.2" + resolved "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.45.2.tgz" + integrity sha512-7Ehow/7Wp3aoyCrZwQpU7a2CnoMq0XhIcioFuKjBb0PLYfBfmTsFTUyatlHu0fRxhwcRsSQRTvEhmZu8CppBpQ== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-http@0.57.2": + version "0.57.2" + resolved "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.57.2.tgz" + integrity sha512-1Uz5iJ9ZAlFOiPuwYg29Bf7bJJc/GeoeJIFKJYQf67nTVKFe8RHbEtxgkOmK4UGZNHKXcpW4P8cWBYzBn1USpg== + dependencies: + "@opentelemetry/core" "1.30.1" + "@opentelemetry/instrumentation" "0.57.2" + "@opentelemetry/semantic-conventions" "1.28.0" + forwarded-parse "2.1.2" + semver "^7.5.2" + +"@opentelemetry/instrumentation-ioredis@0.47.1": + version "0.47.1" + resolved "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.47.1.tgz" + integrity sha512-OtFGSN+kgk/aoKgdkKQnBsQFDiG8WdCxu+UrHr0bXScdAmtSzLSraLo7wFIb25RVHfRWvzI5kZomqJYEg/l1iA== + dependencies: + "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/redis-common" "^0.36.2" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-kafkajs@0.7.1": + version "0.7.1" + resolved "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.7.1.tgz" + integrity sha512-OtjaKs8H7oysfErajdYr1yuWSjMAectT7Dwr+axIoZqT9lmEOkD/H/3rgAs8h/NIuEi2imSXD+vL4MZtOuJfqQ== + dependencies: + "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-knex@0.44.1": + version "0.44.1" + resolved "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.44.1.tgz" + integrity sha512-U4dQxkNhvPexffjEmGwCq68FuftFK15JgUF05y/HlK3M6W/G2iEaACIfXdSnwVNe9Qh0sPfw8LbOPxrWzGWGMQ== + dependencies: + "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-koa@0.47.1": + version "0.47.1" + resolved "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.47.1.tgz" + integrity sha512-l/c+Z9F86cOiPJUllUCt09v+kICKvT+Vg1vOAJHtHPsJIzurGayucfCMq2acd/A/yxeNWunl9d9eqZ0G+XiI6A== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-lru-memoizer@0.44.1": + version "0.44.1" + resolved "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.44.1.tgz" + integrity sha512-5MPkYCvG2yw7WONEjYj5lr5JFehTobW7wX+ZUFy81oF2lr9IPfZk9qO+FTaM0bGEiymwfLwKe6jE15nHn1nmHg== + dependencies: + "@opentelemetry/instrumentation" "^0.57.1" + +"@opentelemetry/instrumentation-mongodb@0.52.0": + version "0.52.0" + resolved "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.52.0.tgz" + integrity sha512-1xmAqOtRUQGR7QfJFfGV/M2kC7wmI2WgZdpru8hJl3S0r4hW0n3OQpEHlSGXJAaNFyvT+ilnwkT+g5L4ljHR6g== + dependencies: + "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-mongoose@0.46.1": + version "0.46.1" + resolved "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.46.1.tgz" + integrity sha512-3kINtW1LUTPkiXFRSSBmva1SXzS/72we/jL22N+BnF3DFcoewkdkHPYOIdAAk9gSicJ4d5Ojtt1/HeibEc5OQg== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-mysql2@0.45.2": + version "0.45.2" + resolved "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.45.2.tgz" + integrity sha512-h6Ad60FjCYdJZ5DTz1Lk2VmQsShiViKe0G7sYikb0GHI0NVvApp2XQNRHNjEMz87roFttGPLHOYVPlfy+yVIhQ== + dependencies: + "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/semantic-conventions" "^1.27.0" + "@opentelemetry/sql-common" "^0.40.1" + +"@opentelemetry/instrumentation-mysql@0.45.1": + version "0.45.1" + resolved "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.45.1.tgz" + integrity sha512-TKp4hQ8iKQsY7vnp/j0yJJ4ZsP109Ht6l4RHTj0lNEG1TfgTrIH5vJMbgmoYXWzNHAqBH2e7fncN12p3BP8LFg== + dependencies: + "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/semantic-conventions" "^1.27.0" + "@types/mysql" "2.15.26" + +"@opentelemetry/instrumentation-pg@0.51.1": + version "0.51.1" + resolved "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.51.1.tgz" + integrity sha512-QxgjSrxyWZc7Vk+qGSfsejPVFL1AgAJdSBMYZdDUbwg730D09ub3PXScB9d04vIqPriZ+0dqzjmQx0yWKiCi2Q== + dependencies: + "@opentelemetry/core" "^1.26.0" + "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/semantic-conventions" "^1.27.0" + "@opentelemetry/sql-common" "^0.40.1" + "@types/pg" "8.6.1" + "@types/pg-pool" "2.0.6" + +"@opentelemetry/instrumentation-redis-4@0.46.1": + version "0.46.1" + resolved "https://registry.npmjs.org/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.46.1.tgz" + integrity sha512-UMqleEoabYMsWoTkqyt9WAzXwZ4BlFZHO40wr3d5ZvtjKCHlD4YXLm+6OLCeIi/HkX7EXvQaz8gtAwkwwSEvcQ== + dependencies: + "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/redis-common" "^0.36.2" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-tedious@0.18.1": + version "0.18.1" + resolved "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.18.1.tgz" + integrity sha512-5Cuy/nj0HBaH+ZJ4leuD7RjgvA844aY2WW+B5uLcWtxGjRZl3MNLuxnNg5DYWZNPO+NafSSnra0q49KWAHsKBg== + dependencies: + "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/semantic-conventions" "^1.27.0" + "@types/tedious" "^4.0.14" + +"@opentelemetry/instrumentation-undici@0.10.1": + version "0.10.1" + resolved "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.10.1.tgz" + integrity sha512-rkOGikPEyRpMCmNu9AQuV5dtRlDmJp2dK5sw8roVshAGoB6hH/3QjDtRhdwd75SsJwgynWUNRUYe0wAkTo16tQ== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.57.1" + +"@opentelemetry/instrumentation@0.57.2", "@opentelemetry/instrumentation@^0.52.0 || ^0.53.0 || ^0.54.0 || ^0.55.0 || ^0.56.0 || ^0.57.0", "@opentelemetry/instrumentation@^0.57.1", "@opentelemetry/instrumentation@^0.57.2": + version "0.57.2" + resolved "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.57.2.tgz" + integrity sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg== + dependencies: + "@opentelemetry/api-logs" "0.57.2" + "@types/shimmer" "^1.2.0" + import-in-the-middle "^1.8.1" + require-in-the-middle "^7.1.1" + semver "^7.5.2" + shimmer "^1.2.1" + +"@opentelemetry/redis-common@^0.36.2": + version "0.36.2" + resolved "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.36.2.tgz" + integrity sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g== + +"@opentelemetry/resources@1.30.1", "@opentelemetry/resources@^1.30.1": + version "1.30.1" + resolved "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz" + integrity sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA== + dependencies: + "@opentelemetry/core" "1.30.1" + "@opentelemetry/semantic-conventions" "1.28.0" + +"@opentelemetry/sdk-trace-base@^1.30.1": + version "1.30.1" + resolved "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.30.1.tgz" + integrity sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg== + dependencies: + "@opentelemetry/core" "1.30.1" + "@opentelemetry/resources" "1.30.1" + "@opentelemetry/semantic-conventions" "1.28.0" + +"@opentelemetry/semantic-conventions@1.28.0": + version "1.28.0" + resolved "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz" + integrity sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA== + +"@opentelemetry/semantic-conventions@^1.27.0", "@opentelemetry/semantic-conventions@^1.34.0": + version "1.34.0" + resolved "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.34.0.tgz" + integrity sha512-aKcOkyrorBGlajjRdVoJWHTxfxO1vCNHLJVlSDaRHDIdjU+pX8IYQPvPDkYiujKLbRnWU+1TBwEt0QRgSm4SGA== + +"@opentelemetry/sql-common@^0.40.1": + version "0.40.1" + resolved "https://registry.npmjs.org/@opentelemetry/sql-common/-/sql-common-0.40.1.tgz" + integrity sha512-nSDlnHSqzC3pXn/wZEZVLuAuJ1MYMXPBwtv2qAbCa3847SaHItdE7SzUq/Jtb0KZmh1zfAbNi3AAMjztTT4Ugg== + dependencies: + "@opentelemetry/core" "^1.1.0" + +"@pkgr/core@^0.2.4": + version "0.2.4" + resolved "https://registry.npmjs.org/@pkgr/core/-/core-0.2.4.tgz" + integrity sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw== + +"@playwright/test@1.52.0": + version "1.52.0" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.52.0.tgz#267ec595b43a8f4fa5e444ea503689629e91a5b8" + integrity sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g== + dependencies: + playwright "1.52.0" + +"@prisma/instrumentation@6.8.2": + version "6.8.2" + resolved "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-6.8.2.tgz" + integrity sha512-5NCTbZjw7a+WIZ/ey6G8SY+YKcyM2zBF0hOT1muvqC9TbVtTCr5Qv3RL/2iNDOzLUHEvo4I1uEfioyfuNOGK8Q== + dependencies: + "@opentelemetry/instrumentation" "^0.52.0 || ^0.53.0 || ^0.54.0 || ^0.55.0 || ^0.56.0 || ^0.57.0" + +"@radix-ui/primitive@1.1.2": + version "1.1.2" + resolved "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz" + integrity sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA== + +"@radix-ui/react-compose-refs@1.1.2", "@radix-ui/react-compose-refs@^1.1.1": + version "1.1.2" + resolved "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz" + integrity sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg== + +"@radix-ui/react-context@1.1.2": + version "1.1.2" + resolved "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz" + integrity sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA== + +"@radix-ui/react-dialog@^1.1.2", "@radix-ui/react-dialog@^1.1.6": + version "1.1.13" + resolved "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.13.tgz" + integrity sha512-ARFmqUyhIVS3+riWzwGTe7JLjqwqgnODBUZdqpWar/z1WFs9z76fuOs/2BOWCR+YboRn4/WN9aoaGVwqNRr8VA== + dependencies: + "@radix-ui/primitive" "1.1.2" + "@radix-ui/react-compose-refs" "1.1.2" + "@radix-ui/react-context" "1.1.2" + "@radix-ui/react-dismissable-layer" "1.1.9" + "@radix-ui/react-focus-guards" "1.1.2" + "@radix-ui/react-focus-scope" "1.1.6" + "@radix-ui/react-id" "1.1.1" + "@radix-ui/react-portal" "1.1.8" + "@radix-ui/react-presence" "1.1.4" + "@radix-ui/react-primitive" "2.1.2" + "@radix-ui/react-slot" "1.2.2" + "@radix-ui/react-use-controllable-state" "1.2.2" + aria-hidden "^1.2.4" + react-remove-scroll "^2.6.3" + +"@radix-ui/react-dismissable-layer@1.1.9": + version "1.1.9" + resolved "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.9.tgz" + integrity sha512-way197PiTvNp+WBP7svMJasHl+vibhWGQDb6Mgf5mhEWJkgb85z7Lfl9TUdkqpWsf8GRNmoopx9ZxCyDzmgRMQ== + dependencies: + "@radix-ui/primitive" "1.1.2" + "@radix-ui/react-compose-refs" "1.1.2" + "@radix-ui/react-primitive" "2.1.2" + "@radix-ui/react-use-callback-ref" "1.1.1" + "@radix-ui/react-use-escape-keydown" "1.1.1" + +"@radix-ui/react-focus-guards@1.1.2": + version "1.1.2" + resolved "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.2.tgz" + integrity sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA== + +"@radix-ui/react-focus-scope@1.1.6": + version "1.1.6" + resolved "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.6.tgz" + integrity sha512-r9zpYNUQY+2jWHWZGyddQLL9YHkM/XvSFHVcWs7bdVuxMAnCwTAuy6Pf47Z4nw7dYcUou1vg/VgjjrrH03VeBw== + dependencies: + "@radix-ui/react-compose-refs" "1.1.2" + "@radix-ui/react-primitive" "2.1.2" + "@radix-ui/react-use-callback-ref" "1.1.1" + +"@radix-ui/react-id@1.1.1", "@radix-ui/react-id@^1.1.0": + version "1.1.1" + resolved "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz" + integrity sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg== + dependencies: + "@radix-ui/react-use-layout-effect" "1.1.1" + +"@radix-ui/react-portal@1.1.8": + version "1.1.8" + resolved "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.8.tgz" + integrity sha512-hQsTUIn7p7fxCPvao/q6wpbxmCwgLrlz+nOrJgC+RwfZqWY/WN+UMqkXzrtKbPrF82P43eCTl3ekeKuyAQbFeg== + dependencies: + "@radix-ui/react-primitive" "2.1.2" + "@radix-ui/react-use-layout-effect" "1.1.1" + +"@radix-ui/react-presence@1.1.4": + version "1.1.4" + resolved "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz" + integrity sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA== + dependencies: + "@radix-ui/react-compose-refs" "1.1.2" + "@radix-ui/react-use-layout-effect" "1.1.1" + +"@radix-ui/react-primitive@2.1.2", "@radix-ui/react-primitive@^2.0.0", "@radix-ui/react-primitive@^2.0.2": + version "2.1.2" + resolved "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.2.tgz" + integrity sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw== + dependencies: + "@radix-ui/react-slot" "1.2.2" + +"@radix-ui/react-slot@1.2.2": + version "1.2.2" + resolved "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.2.tgz" + integrity sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ== + dependencies: + "@radix-ui/react-compose-refs" "1.1.2" + +"@radix-ui/react-use-callback-ref@1.1.1": + version "1.1.1" + resolved "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz" + integrity sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg== + +"@radix-ui/react-use-controllable-state@1.2.2": + version "1.2.2" + resolved "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz" + integrity sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg== + dependencies: + "@radix-ui/react-use-effect-event" "0.0.2" + "@radix-ui/react-use-layout-effect" "1.1.1" + +"@radix-ui/react-use-effect-event@0.0.2": + version "0.0.2" + resolved "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz" + integrity sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA== + dependencies: + "@radix-ui/react-use-layout-effect" "1.1.1" + +"@radix-ui/react-use-escape-keydown@1.1.1": + version "1.1.1" + resolved "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz" + integrity sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g== + dependencies: + "@radix-ui/react-use-callback-ref" "1.1.1" + +"@radix-ui/react-use-layout-effect@1.1.1": + version "1.1.1" + resolved "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz" + integrity sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ== + +"@react-aria/autocomplete@3.0.0-beta.2": + version "3.0.0-beta.2" + resolved "https://registry.npmjs.org/@react-aria/autocomplete/-/autocomplete-3.0.0-beta.2.tgz" + integrity sha512-oxsFCIGj5yooQkZzdqjvsdfr9fOlmAq4v6njIOAyQFsta3H0yQiv+YU3XnrnCBxVX+Mz/mZtZgfhAA9JBDukHg== + dependencies: + "@react-aria/combobox" "^3.12.2" + "@react-aria/focus" "^3.20.2" + "@react-aria/i18n" "^3.12.8" + "@react-aria/interactions" "^3.25.0" + "@react-aria/listbox" "^3.14.3" + "@react-aria/searchfield" "^3.8.3" + "@react-aria/textfield" "^3.17.2" + "@react-aria/utils" "^3.28.2" + "@react-stately/autocomplete" "3.0.0-beta.1" + "@react-stately/combobox" "^3.10.4" + "@react-types/autocomplete" "3.0.0-alpha.30" + "@react-types/button" "^3.12.0" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/autocomplete@3.0.0-beta.3": + version "3.0.0-beta.3" + resolved "https://registry.npmjs.org/@react-aria/autocomplete/-/autocomplete-3.0.0-beta.3.tgz" + integrity sha512-8haBygHNMqVt4Ge90VOk+iVlLW+zhiOGHYz2IKCE6+Sy1dTE6mzhHjxrtwWYnSez/OQLbxjHlwLch4CDd5JkLA== + dependencies: + "@react-aria/combobox" "^3.12.3" + "@react-aria/focus" "^3.20.3" + "@react-aria/i18n" "^3.12.9" + "@react-aria/interactions" "^3.25.1" + "@react-aria/listbox" "^3.14.4" + "@react-aria/searchfield" "^3.8.4" + "@react-aria/textfield" "^3.17.3" + "@react-aria/utils" "^3.29.0" + "@react-stately/autocomplete" "3.0.0-beta.1" + "@react-stately/combobox" "^3.10.5" + "@react-types/autocomplete" "3.0.0-alpha.31" + "@react-types/button" "^3.12.1" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/breadcrumbs@^3.5.19": + version "3.5.23" + resolved "https://registry.npmjs.org/@react-aria/breadcrumbs/-/breadcrumbs-3.5.23.tgz" + integrity sha512-4uLxuAgPfXds8sBc/Cg0ml7LKWzK+YTwHL7xclhQUkPO32rzlHDl+BJ5cyWhvZgGUf8JJXbXhD5VlJJzbbl8Xg== + dependencies: + "@react-aria/i18n" "^3.12.8" + "@react-aria/link" "^3.8.0" + "@react-aria/utils" "^3.28.2" + "@react-types/breadcrumbs" "^3.7.12" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/breadcrumbs@^3.5.23", "@react-aria/breadcrumbs@^3.5.24": + version "3.5.24" + resolved "https://registry.npmjs.org/@react-aria/breadcrumbs/-/breadcrumbs-3.5.24.tgz" + integrity sha512-CRheGyyM8afPJvDHLXn/mmGG/WAr/z2LReK3DlPdxVKcsOn7g3NIRxAcAIAJQlDLdOiu1SXHiZe6uu2jPhHrxA== + dependencies: + "@react-aria/i18n" "^3.12.9" + "@react-aria/link" "^3.8.1" + "@react-aria/utils" "^3.29.0" + "@react-types/breadcrumbs" "^3.7.13" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/button@^3.11.0": + version "3.13.0" + resolved "https://registry.npmjs.org/@react-aria/button/-/button-3.13.0.tgz" + integrity sha512-BEcTQb7Q8ZrAtn0scPDv/ErZoGC1FI0sLk0UTPGskuh/RV9ZZGFbuSWTqOwV8w5CS6VMvPjH6vaE8hS7sb5DIw== + dependencies: + "@react-aria/interactions" "^3.25.0" + "@react-aria/toolbar" "3.0.0-beta.15" + "@react-aria/utils" "^3.28.2" + "@react-stately/toggle" "^3.8.3" + "@react-types/button" "^3.12.0" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/button@^3.13.0", "@react-aria/button@^3.13.1": + version "3.13.1" + resolved "https://registry.npmjs.org/@react-aria/button/-/button-3.13.1.tgz" + integrity sha512-E49qcbBRgofXYfWbli50bepWVNtQBq7qewL9XsX7nHkwPPUe1IRwJOnWZqYMgwwhUBOXfnsR6/TssiXqZsrJdw== + dependencies: + "@react-aria/interactions" "^3.25.1" + "@react-aria/toolbar" "3.0.0-beta.16" + "@react-aria/utils" "^3.29.0" + "@react-stately/toggle" "^3.8.4" + "@react-types/button" "^3.12.1" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/calendar@3.6.0", "@react-aria/calendar@^3.6.0": + version "3.6.0" + resolved "https://registry.npmjs.org/@react-aria/calendar/-/calendar-3.6.0.tgz" + integrity sha512-tZ3nd5DP8uxckbj83Pt+4RqgcTWDlGi7njzc7QqFOG2ApfnYDUXbIpb/Q4KY6JNlJskG8q33wo0XfOwNy8J+eg== + dependencies: + "@internationalized/date" "^3.6.0" + "@react-aria/i18n" "^3.12.4" + "@react-aria/interactions" "^3.22.5" + "@react-aria/live-announcer" "^3.4.1" + "@react-aria/utils" "^3.26.0" + "@react-stately/calendar" "^3.6.0" + "@react-types/button" "^3.10.1" + "@react-types/calendar" "^3.5.0" + "@react-types/shared" "^3.26.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/calendar@3.8.0", "@react-aria/calendar@^3.8.0": + version "3.8.0" + resolved "https://registry.npmjs.org/@react-aria/calendar/-/calendar-3.8.0.tgz" + integrity sha512-9vms/fWjJPZkJcMxciwWWOjGy/Q0nqI6FV0pYbMZbqepkzglEaVd98kl506r/4hLhWKwLdTfqCgbntRecj8jBg== + dependencies: + "@internationalized/date" "^3.8.0" + "@react-aria/i18n" "^3.12.8" + "@react-aria/interactions" "^3.25.0" + "@react-aria/live-announcer" "^3.4.2" + "@react-aria/utils" "^3.28.2" + "@react-stately/calendar" "^3.8.0" + "@react-types/button" "^3.12.0" + "@react-types/calendar" "^3.7.0" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/calendar@^3.8.1": + version "3.8.1" + resolved "https://registry.npmjs.org/@react-aria/calendar/-/calendar-3.8.1.tgz" + integrity sha512-S931yi8jJ6CgUQJk+h/PEl+V0n1dUYr9n6nKXmZeU3940to4DauqwvmD9sg67hFHJ0QGroHT/s29yIfa5MfQcg== + dependencies: + "@internationalized/date" "^3.8.1" + "@react-aria/i18n" "^3.12.9" + "@react-aria/interactions" "^3.25.1" + "@react-aria/live-announcer" "^3.4.2" + "@react-aria/utils" "^3.29.0" + "@react-stately/calendar" "^3.8.1" + "@react-types/button" "^3.12.1" + "@react-types/calendar" "^3.7.1" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/checkbox@^3.15.0": + version "3.15.4" + resolved "https://registry.npmjs.org/@react-aria/checkbox/-/checkbox-3.15.4.tgz" + integrity sha512-ZkDJFs2EfMBXVIpBSo4ouB+NXyr2LRgZNp2x8/v+7n3aTmMU8j2PzT+Ra2geTQbC0glMP7UrSg4qZblqrxEBcQ== + dependencies: + "@react-aria/form" "^3.0.15" + "@react-aria/interactions" "^3.25.0" + "@react-aria/label" "^3.7.17" + "@react-aria/toggle" "^3.11.2" + "@react-aria/utils" "^3.28.2" + "@react-stately/checkbox" "^3.6.13" + "@react-stately/form" "^3.1.3" + "@react-stately/toggle" "^3.8.3" + "@react-types/checkbox" "^3.9.3" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/checkbox@^3.15.4", "@react-aria/checkbox@^3.15.5": + version "3.15.5" + resolved "https://registry.npmjs.org/@react-aria/checkbox/-/checkbox-3.15.5.tgz" + integrity sha512-b9c76DBSYTdacSogbsvjkdZomTo5yhBNMmR5ufO544HQ718Ry8q8JmVbtmF/+dkZN7KGnBQCltzGLzXH0Vc0Zg== + dependencies: + "@react-aria/form" "^3.0.16" + "@react-aria/interactions" "^3.25.1" + "@react-aria/label" "^3.7.18" + "@react-aria/toggle" "^3.11.3" + "@react-aria/utils" "^3.29.0" + "@react-stately/checkbox" "^3.6.14" + "@react-stately/form" "^3.1.4" + "@react-stately/toggle" "^3.8.4" + "@react-types/checkbox" "^3.9.4" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/collections@3.0.0-alpha.6": + version "3.0.0-alpha.6" + resolved "https://registry.npmjs.org/@react-aria/collections/-/collections-3.0.0-alpha.6.tgz" + integrity sha512-A+7Eap/zvsghMb5/C3EAPn41axSzRhtX2glQRXSBj1mK31CTPCZ9BhrMIMC5DL7ZnfA7C+Ysilo9nI2YQh5PMg== + dependencies: + "@react-aria/ssr" "^3.9.7" + "@react-aria/utils" "^3.26.0" + "@react-types/shared" "^3.26.0" + "@swc/helpers" "^0.5.0" + use-sync-external-store "^1.2.0" + +"@react-aria/collections@3.0.0-rc.0": + version "3.0.0-rc.0" + resolved "https://registry.npmjs.org/@react-aria/collections/-/collections-3.0.0-rc.0.tgz" + integrity sha512-WcRcE3wKtbprOJlBaMbdYS5Suu2KIGq1gVT2fLXVbmDY0CjGemqp2m5aDblQOO8pxvsAqHV8pyznkhANTnK1CQ== + dependencies: + "@react-aria/interactions" "^3.25.0" + "@react-aria/ssr" "^3.9.8" + "@react-aria/utils" "^3.28.2" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + use-sync-external-store "^1.4.0" + +"@react-aria/collections@3.0.0-rc.1": + version "3.0.0-rc.1" + resolved "https://registry.npmjs.org/@react-aria/collections/-/collections-3.0.0-rc.1.tgz" + integrity sha512-R8FE4z82lsXsNJgMu545U9BzDlnjEOVh8hDFWDwFFTf/NZSPw2ESgEZhFnVusvn5mHtr4mTzb2MyfGY5E2wVYw== + dependencies: + "@react-aria/interactions" "^3.25.1" + "@react-aria/ssr" "^3.9.8" + "@react-aria/utils" "^3.29.0" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + use-sync-external-store "^1.4.0" + +"@react-aria/color@^3.0.2": + version "3.0.6" + resolved "https://registry.npmjs.org/@react-aria/color/-/color-3.0.6.tgz" + integrity sha512-ik4Db9hrN1yIT0CQMB888ktBmrwA/kNhkfiDACtoUHv8Ev+YEpmagnmih9vMyW2vcnozYJpnn/aCMl59J5uMew== + dependencies: + "@react-aria/i18n" "^3.12.8" + "@react-aria/interactions" "^3.25.0" + "@react-aria/numberfield" "^3.11.13" + "@react-aria/slider" "^3.7.18" + "@react-aria/spinbutton" "^3.6.14" + "@react-aria/textfield" "^3.17.2" + "@react-aria/utils" "^3.28.2" + "@react-aria/visually-hidden" "^3.8.22" + "@react-stately/color" "^3.8.4" + "@react-stately/form" "^3.1.3" + "@react-types/color" "^3.0.4" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/color@^3.0.6", "@react-aria/color@^3.0.7": + version "3.0.7" + resolved "https://registry.npmjs.org/@react-aria/color/-/color-3.0.7.tgz" + integrity sha512-3DcYxEWBrcuHSBq0OqCs6GySuy6eOue8/ngC31j/8aMXR+O4mGpXi0wo3rSQGFmGq/4Ri986cI2iGwZOkzpMHg== + dependencies: + "@react-aria/i18n" "^3.12.9" + "@react-aria/interactions" "^3.25.1" + "@react-aria/numberfield" "^3.11.14" + "@react-aria/slider" "^3.7.19" + "@react-aria/spinbutton" "^3.6.15" + "@react-aria/textfield" "^3.17.3" + "@react-aria/utils" "^3.29.0" + "@react-aria/visually-hidden" "^3.8.23" + "@react-stately/color" "^3.8.5" + "@react-stately/form" "^3.1.4" + "@react-types/color" "^3.0.5" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/combobox@^3.11.0": + version "3.12.2" + resolved "https://registry.npmjs.org/@react-aria/combobox/-/combobox-3.12.2.tgz" + integrity sha512-EgddiF8VnAjB4EynJERPn4IoDMUabI8GiKOQZ6Ar3MlRWxQnUfxPpZwXs8qWR3dPCzYUt2PhBinhBMjyR1yRIw== + dependencies: + "@react-aria/focus" "^3.20.2" + "@react-aria/i18n" "^3.12.8" + "@react-aria/listbox" "^3.14.3" + "@react-aria/live-announcer" "^3.4.2" + "@react-aria/menu" "^3.18.2" + "@react-aria/overlays" "^3.27.0" + "@react-aria/selection" "^3.24.0" + "@react-aria/textfield" "^3.17.2" + "@react-aria/utils" "^3.28.2" + "@react-stately/collections" "^3.12.3" + "@react-stately/combobox" "^3.10.4" + "@react-stately/form" "^3.1.3" + "@react-types/button" "^3.12.0" + "@react-types/combobox" "^3.13.4" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/combobox@^3.12.2", "@react-aria/combobox@^3.12.3": + version "3.12.3" + resolved "https://registry.npmjs.org/@react-aria/combobox/-/combobox-3.12.3.tgz" + integrity sha512-nCLFSQjOR3r3tB1AURtZKSZhi2euBMw0QxsIjnMVF73BQOfwfHMrIFctNULbL070gEnXofzeBd3ykJQpnsGH+Q== + dependencies: + "@react-aria/focus" "^3.20.3" + "@react-aria/i18n" "^3.12.9" + "@react-aria/listbox" "^3.14.4" + "@react-aria/live-announcer" "^3.4.2" + "@react-aria/menu" "^3.18.3" + "@react-aria/overlays" "^3.27.1" + "@react-aria/selection" "^3.24.1" + "@react-aria/textfield" "^3.17.3" + "@react-aria/utils" "^3.29.0" + "@react-stately/collections" "^3.12.4" + "@react-stately/combobox" "^3.10.5" + "@react-stately/form" "^3.1.4" + "@react-types/button" "^3.12.1" + "@react-types/combobox" "^3.13.5" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/datepicker@3.12.0", "@react-aria/datepicker@^3.12.0": + version "3.12.0" + resolved "https://registry.npmjs.org/@react-aria/datepicker/-/datepicker-3.12.0.tgz" + integrity sha512-VYNXioLfddIHpwQx211+rTYuunDmI7VHWBRetCpH3loIsVFuhFSRchTQpclAzxolO3g0vO7pMVj9VYt7Swp6kg== + dependencies: + "@internationalized/date" "^3.6.0" + "@internationalized/number" "^3.6.0" + "@internationalized/string" "^3.2.5" + "@react-aria/focus" "^3.19.0" + "@react-aria/form" "^3.0.11" + "@react-aria/i18n" "^3.12.4" + "@react-aria/interactions" "^3.22.5" + "@react-aria/label" "^3.7.13" + "@react-aria/spinbutton" "^3.6.10" + "@react-aria/utils" "^3.26.0" + "@react-stately/datepicker" "^3.11.0" + "@react-stately/form" "^3.1.0" + "@react-types/button" "^3.10.1" + "@react-types/calendar" "^3.5.0" + "@react-types/datepicker" "^3.9.0" + "@react-types/dialog" "^3.5.14" + "@react-types/shared" "^3.26.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/datepicker@3.14.2", "@react-aria/datepicker@^3.14.2": + version "3.14.2" + resolved "https://registry.npmjs.org/@react-aria/datepicker/-/datepicker-3.14.2.tgz" + integrity sha512-O7fdzcqIJ7i/+8SGYvx4tloTZgK4Ws8OChdbFcd2rZoRPqxM50M6J+Ota8hTet2wIhojUXnM3x2na3EvoucBXA== + dependencies: + "@internationalized/date" "^3.8.0" + "@internationalized/number" "^3.6.1" + "@internationalized/string" "^3.2.6" + "@react-aria/focus" "^3.20.2" + "@react-aria/form" "^3.0.15" + "@react-aria/i18n" "^3.12.8" + "@react-aria/interactions" "^3.25.0" + "@react-aria/label" "^3.7.17" + "@react-aria/spinbutton" "^3.6.14" + "@react-aria/utils" "^3.28.2" + "@react-stately/datepicker" "^3.14.0" + "@react-stately/form" "^3.1.3" + "@react-types/button" "^3.12.0" + "@react-types/calendar" "^3.7.0" + "@react-types/datepicker" "^3.12.0" + "@react-types/dialog" "^3.5.17" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/datepicker@^3.14.3": + version "3.14.3" + resolved "https://registry.npmjs.org/@react-aria/datepicker/-/datepicker-3.14.3.tgz" + integrity sha512-gDc+bM0EaY3BuIW8IJu/ARJV78bRpOaHp+B08EW4N2qJvc7Bs+EmGLnxMrB6Ny+YxNxsYdQRA/FqiytVYOEk8w== + dependencies: + "@internationalized/date" "^3.8.1" + "@internationalized/number" "^3.6.2" + "@internationalized/string" "^3.2.6" + "@react-aria/focus" "^3.20.3" + "@react-aria/form" "^3.0.16" + "@react-aria/i18n" "^3.12.9" + "@react-aria/interactions" "^3.25.1" + "@react-aria/label" "^3.7.18" + "@react-aria/spinbutton" "^3.6.15" + "@react-aria/utils" "^3.29.0" + "@react-stately/datepicker" "^3.14.1" + "@react-stately/form" "^3.1.4" + "@react-types/button" "^3.12.1" + "@react-types/calendar" "^3.7.1" + "@react-types/datepicker" "^3.12.1" + "@react-types/dialog" "^3.5.18" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/dialog@^3.5.20": + version "3.5.24" + resolved "https://registry.npmjs.org/@react-aria/dialog/-/dialog-3.5.24.tgz" + integrity sha512-tw0WH89gVpHMI5KUQhuzRE+IYCc9clRfDvCppuXNueKDrZmrQKbeoU6d0b5WYRsBur2+d7ErtvpLzHVqE1HzfA== + dependencies: + "@react-aria/interactions" "^3.25.0" + "@react-aria/overlays" "^3.27.0" + "@react-aria/utils" "^3.28.2" + "@react-types/dialog" "^3.5.17" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/dialog@^3.5.24", "@react-aria/dialog@^3.5.25": + version "3.5.25" + resolved "https://registry.npmjs.org/@react-aria/dialog/-/dialog-3.5.25.tgz" + integrity sha512-hVP/TvjUnPgckg4qibc/TDH54O+BzW95hxApxBw1INyViRm95PxdCQDqBdQ/ZW7Gv6J2aUBCGihX7kINPf70ow== + dependencies: + "@react-aria/interactions" "^3.25.1" + "@react-aria/overlays" "^3.27.1" + "@react-aria/utils" "^3.29.0" + "@react-types/dialog" "^3.5.18" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/disclosure@^3.0.0": + version "3.0.4" + resolved "https://registry.npmjs.org/@react-aria/disclosure/-/disclosure-3.0.4.tgz" + integrity sha512-HXGVLA06BH0b/gN8dCTzWATwMikz8D+ahRxZiI0HDZxLADWGsSPqRXKN0GNAiBKbvPtvAbrwslE3pktk/SlU/w== + dependencies: + "@react-aria/ssr" "^3.9.8" + "@react-aria/utils" "^3.28.2" + "@react-stately/disclosure" "^3.0.3" + "@react-types/button" "^3.12.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/disclosure@^3.0.4", "@react-aria/disclosure@^3.0.5": + version "3.0.5" + resolved "https://registry.npmjs.org/@react-aria/disclosure/-/disclosure-3.0.5.tgz" + integrity sha512-YrazXoIzVq48soJpVMb2Iq/CB+lglwfKLsml5UfpE0MGlJJ/jWtIZtodqQ8ree1YguMNTvtESazTlMo7ZLsasQ== + dependencies: + "@react-aria/ssr" "^3.9.8" + "@react-aria/utils" "^3.29.0" + "@react-stately/disclosure" "^3.0.4" + "@react-types/button" "^3.12.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/dnd@^3.8.0": + version "3.9.2" + resolved "https://registry.npmjs.org/@react-aria/dnd/-/dnd-3.9.2.tgz" + integrity sha512-pPYygmJTjSPV2K/r48TvF75WuddG8d8nlIxAXSW22++WKqZ0z+eun6gDUXoKeB2rgY7sVfLqpRdnPV52AnBX+Q== + dependencies: + "@internationalized/string" "^3.2.6" + "@react-aria/i18n" "^3.12.8" + "@react-aria/interactions" "^3.25.0" + "@react-aria/live-announcer" "^3.4.2" + "@react-aria/overlays" "^3.27.0" + "@react-aria/utils" "^3.28.2" + "@react-stately/dnd" "^3.5.3" + "@react-types/button" "^3.12.0" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/dnd@^3.9.2", "@react-aria/dnd@^3.9.3": + version "3.9.3" + resolved "https://registry.npmjs.org/@react-aria/dnd/-/dnd-3.9.3.tgz" + integrity sha512-Sjb+UQxG58/paOZXsVKiqLautV4FyILr3tLxMG4Q04QOUzatqlz91APt7RsVMdizk6bVB7Lg74AEypHbXVzhDQ== + dependencies: + "@internationalized/string" "^3.2.6" + "@react-aria/i18n" "^3.12.9" + "@react-aria/interactions" "^3.25.1" + "@react-aria/live-announcer" "^3.4.2" + "@react-aria/overlays" "^3.27.1" + "@react-aria/utils" "^3.29.0" + "@react-stately/dnd" "^3.5.4" + "@react-types/button" "^3.12.1" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/focus@^3.19.0": + version "3.20.2" + resolved "https://registry.npmjs.org/@react-aria/focus/-/focus-3.20.2.tgz" + integrity sha512-Q3rouk/rzoF/3TuH6FzoAIKrl+kzZi9LHmr8S5EqLAOyP9TXIKG34x2j42dZsAhrw7TbF9gA8tBKwnCNH4ZV+Q== + dependencies: + "@react-aria/interactions" "^3.25.0" + "@react-aria/utils" "^3.28.2" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + clsx "^2.0.0" + +"@react-aria/focus@^3.20.2", "@react-aria/focus@^3.20.3": + version "3.20.3" + resolved "https://registry.npmjs.org/@react-aria/focus/-/focus-3.20.3.tgz" + integrity sha512-rR5uZUMSY4xLHmpK/I8bP1V6vUNHFo33gTvrvNUsAKKqvMfa7R2nu5A6v97dr5g6tVH6xzpdkPsOJCWh90H2cw== + dependencies: + "@react-aria/interactions" "^3.25.1" + "@react-aria/utils" "^3.29.0" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + clsx "^2.0.0" + +"@react-aria/form@^3.0.11": + version "3.0.15" + resolved "https://registry.npmjs.org/@react-aria/form/-/form-3.0.15.tgz" + integrity sha512-kk8AnLz+EOgnn3sTaXYmtw+YzVDc1of/+xAkuOupQi6zQFnNRjc99JlDbKHoUZ39urMl+8lsp/1b9VPPhNrBNw== + dependencies: + "@react-aria/interactions" "^3.25.0" + "@react-aria/utils" "^3.28.2" + "@react-stately/form" "^3.1.3" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/form@^3.0.15", "@react-aria/form@^3.0.16": + version "3.0.16" + resolved "https://registry.npmjs.org/@react-aria/form/-/form-3.0.16.tgz" + integrity sha512-N1bDsJfmnyDesayK0Ii6UPH6JWiF6Wz8WSveQ2y5004XHoIWn5LpWmOqnRedvyw4Yedw33schlvrY7ENEwMdpg== + dependencies: + "@react-aria/interactions" "^3.25.1" + "@react-aria/utils" "^3.29.0" + "@react-stately/form" "^3.1.4" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/grid@^3.13.0": + version "3.13.0" + resolved "https://registry.npmjs.org/@react-aria/grid/-/grid-3.13.0.tgz" + integrity sha512-RcuJYA4fyJ83MH3SunU+P5BGkx3LJdQ6kxwqwWGIuI9eUKc7uVbqvN9WN3fI+L0QfxqBFmh7ffRxIdQn7puuzw== + dependencies: + "@react-aria/focus" "^3.20.2" + "@react-aria/i18n" "^3.12.8" + "@react-aria/interactions" "^3.25.0" + "@react-aria/live-announcer" "^3.4.2" + "@react-aria/selection" "^3.24.0" + "@react-aria/utils" "^3.28.2" + "@react-stately/collections" "^3.12.3" + "@react-stately/grid" "^3.11.1" + "@react-stately/selection" "^3.20.1" + "@react-types/checkbox" "^3.9.3" + "@react-types/grid" "^3.3.1" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/grid@^3.14.0": + version "3.14.0" + resolved "https://registry.npmjs.org/@react-aria/grid/-/grid-3.14.0.tgz" + integrity sha512-/tJB7xnSruORJ8tlFHja4SfL8/EW5v4cBLiyD5z48m7IdG33jXR8Cv4Pi5uQqs8zKdnpqZ1wDG3GQxNDwZavpg== + dependencies: + "@react-aria/focus" "^3.20.3" + "@react-aria/i18n" "^3.12.9" + "@react-aria/interactions" "^3.25.1" + "@react-aria/live-announcer" "^3.4.2" + "@react-aria/selection" "^3.24.1" + "@react-aria/utils" "^3.29.0" + "@react-stately/collections" "^3.12.4" + "@react-stately/grid" "^3.11.2" + "@react-stately/selection" "^3.20.2" + "@react-types/checkbox" "^3.9.4" + "@react-types/grid" "^3.3.2" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/gridlist@^3.10.0": + version "3.12.0" + resolved "https://registry.npmjs.org/@react-aria/gridlist/-/gridlist-3.12.0.tgz" + integrity sha512-KSpnSBYQ7ozGQNaRR2NGq7Fl2zIv5w9KNyO9V/IE2mxUNfX6fwqUPoANFcy9ySosksE7pPnFtuYIB+TQtUjYqQ== + dependencies: + "@react-aria/focus" "^3.20.2" + "@react-aria/grid" "^3.13.0" + "@react-aria/i18n" "^3.12.8" + "@react-aria/interactions" "^3.25.0" + "@react-aria/selection" "^3.24.0" + "@react-aria/utils" "^3.28.2" + "@react-stately/collections" "^3.12.3" + "@react-stately/list" "^3.12.1" + "@react-stately/tree" "^3.8.9" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/gridlist@^3.12.0", "@react-aria/gridlist@^3.13.0": + version "3.13.0" + resolved "https://registry.npmjs.org/@react-aria/gridlist/-/gridlist-3.13.0.tgz" + integrity sha512-RHURMo063qbbA8WXCJxGL+5xmSx6yW7Z/V2jycrVcZFOYqj2EgU953aVjpaT/FSyH8/AEioU9oE64YmiEfWUUA== + dependencies: + "@react-aria/focus" "^3.20.3" + "@react-aria/grid" "^3.14.0" + "@react-aria/i18n" "^3.12.9" + "@react-aria/interactions" "^3.25.1" + "@react-aria/selection" "^3.24.1" + "@react-aria/utils" "^3.29.0" + "@react-stately/collections" "^3.12.4" + "@react-stately/list" "^3.12.2" + "@react-stately/tree" "^3.8.10" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/i18n@3.12.4", "@react-aria/i18n@^3.12.4": + version "3.12.4" + resolved "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.12.4.tgz" + integrity sha512-j9+UL3q0Ls8MhXV9gtnKlyozq4aM95YywXqnmJtzT1rYeBx7w28hooqrWkCYLfqr4OIryv1KUnPiCSLwC2OC7w== + dependencies: + "@internationalized/date" "^3.6.0" + "@internationalized/message" "^3.1.6" + "@internationalized/number" "^3.6.0" + "@internationalized/string" "^3.2.5" + "@react-aria/ssr" "^3.9.7" + "@react-aria/utils" "^3.26.0" + "@react-types/shared" "^3.26.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/i18n@3.12.8", "@react-aria/i18n@^3.12.8": + version "3.12.8" + resolved "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.12.8.tgz" + integrity sha512-V/Nau9WuwTwxfFffQL4URyKyY2HhRlu9zmzkF2Hw/j5KmEQemD+9jfaLueG2CJu85lYL06JrZXUdnhZgKnqMkA== + dependencies: + "@internationalized/date" "^3.8.0" + "@internationalized/message" "^3.1.7" + "@internationalized/number" "^3.6.1" + "@internationalized/string" "^3.2.6" + "@react-aria/ssr" "^3.9.8" + "@react-aria/utils" "^3.28.2" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/i18n@^3.12.9": + version "3.12.9" + resolved "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.12.9.tgz" + integrity sha512-Fim0FLfY05kcpIILdOtqcw58c3sksvmVY8kICSwKCuSek4wYfwJdU28p/sRptw4adJhqN8Cbssvkf/J8zL2GgA== + dependencies: + "@internationalized/date" "^3.8.1" + "@internationalized/message" "^3.1.7" + "@internationalized/number" "^3.6.2" + "@internationalized/string" "^3.2.6" + "@react-aria/ssr" "^3.9.8" + "@react-aria/utils" "^3.29.0" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/interactions@^3.22.5": + version "3.25.0" + resolved "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.25.0.tgz" + integrity sha512-GgIsDLlO8rDU/nFn6DfsbP9rfnzhm8QFjZkB9K9+r+MTSCn7bMntiWQgMM+5O6BiA8d7C7x4zuN4bZtc0RBdXQ== + dependencies: + "@react-aria/ssr" "^3.9.8" + "@react-aria/utils" "^3.28.2" + "@react-stately/flags" "^3.1.1" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/interactions@^3.25.0", "@react-aria/interactions@^3.25.1": + version "3.25.1" + resolved "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.25.1.tgz" + integrity sha512-ntLrlgqkmZupbbjekz3fE/n3eQH2vhncx8gUp0+N+GttKWevx7jos11JUBjnJwb1RSOPgRUFcrluOqBp0VgcfQ== + dependencies: + "@react-aria/ssr" "^3.9.8" + "@react-aria/utils" "^3.29.0" + "@react-stately/flags" "^3.1.1" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/label@^3.7.13": + version "3.7.17" + resolved "https://registry.npmjs.org/@react-aria/label/-/label-3.7.17.tgz" + integrity sha512-Fz7IC2LQT2Y/sAoV+gFEXoULtkznzmK2MmeTv5shTNjeTxzB1BhQbD4wyCypi7eGsnD/9Zy+8viULCsIUbvjWw== + dependencies: + "@react-aria/utils" "^3.28.2" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/label@^3.7.17", "@react-aria/label@^3.7.18": + version "3.7.18" + resolved "https://registry.npmjs.org/@react-aria/label/-/label-3.7.18.tgz" + integrity sha512-Ht9D+xkI2Aysn+JNiHE+UZT4FUOGPF7Lfrmp7xdJCA/tEqqF3xW/pAh+UCNOnnWmH8jTYnUg3bCp4G6GQUxKCQ== + dependencies: + "@react-aria/utils" "^3.29.0" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/landmark@^3.0.2", "@react-aria/landmark@^3.0.3": + version "3.0.3" + resolved "https://registry.npmjs.org/@react-aria/landmark/-/landmark-3.0.3.tgz" + integrity sha512-mcmHijInDZZY3W9r0SeRuXsHW8Km9rBWKB3eoBz+PVuyJYMuabhQ2mUB5xTbqbnV++Srr7j/59g+Lbw5gAN4lw== + dependencies: + "@react-aria/utils" "^3.29.0" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + use-sync-external-store "^1.4.0" + +"@react-aria/link@^3.7.7": + version "3.8.0" + resolved "https://registry.npmjs.org/@react-aria/link/-/link-3.8.0.tgz" + integrity sha512-gpDD6t3FqtFR9QjSIKNpmSR3tS4JG2anVKx2wixuRDHO6Ddexxv4SBzsE1+230p+FlFGjftFa2lEgQ7RNjZrmA== + dependencies: + "@react-aria/interactions" "^3.25.0" + "@react-aria/utils" "^3.28.2" + "@react-types/link" "^3.6.0" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/link@^3.8.0", "@react-aria/link@^3.8.1": + version "3.8.1" + resolved "https://registry.npmjs.org/@react-aria/link/-/link-3.8.1.tgz" + integrity sha512-ujq7+XIP7OXHu7m2NObvHsl41B/oIBAYI0D+hsxEQo3+x6Q/OUxp9EX2sX4d7TBWvchFmhr6jJdER0QMmeSO/A== + dependencies: + "@react-aria/interactions" "^3.25.1" + "@react-aria/utils" "^3.29.0" + "@react-types/link" "^3.6.1" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/listbox@^3.13.6": + version "3.14.3" + resolved "https://registry.npmjs.org/@react-aria/listbox/-/listbox-3.14.3.tgz" + integrity sha512-wzelam1KENUvKjsTq8gfrOW2/iab8SyIaSXfFvGmWW82XlDTlW+oQeA39tvOZktMVGspr+xp8FySY09rtz6UXw== + dependencies: + "@react-aria/interactions" "^3.25.0" + "@react-aria/label" "^3.7.17" + "@react-aria/selection" "^3.24.0" + "@react-aria/utils" "^3.28.2" + "@react-stately/collections" "^3.12.3" + "@react-stately/list" "^3.12.1" + "@react-types/listbox" "^3.6.0" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/listbox@^3.14.3", "@react-aria/listbox@^3.14.4": + version "3.14.4" + resolved "https://registry.npmjs.org/@react-aria/listbox/-/listbox-3.14.4.tgz" + integrity sha512-bW3D7KcnQIF77F3zDRMIGQ6e5e1wHTNUtbKJLE423u1Dhc7K2x0pksir0gLGwElhiBW544lY1jv3kFLOeKa6ng== + dependencies: + "@react-aria/interactions" "^3.25.1" + "@react-aria/label" "^3.7.18" + "@react-aria/selection" "^3.24.1" + "@react-aria/utils" "^3.29.0" + "@react-stately/collections" "^3.12.4" + "@react-stately/list" "^3.12.2" + "@react-types/listbox" "^3.7.0" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/live-announcer@^3.4.1", "@react-aria/live-announcer@^3.4.2": + version "3.4.2" + resolved "https://registry.npmjs.org/@react-aria/live-announcer/-/live-announcer-3.4.2.tgz" + integrity sha512-6+yNF9ZrZ4YJ60Oxy2gKI4/xy6WUv1iePDCFJkgpNVuOEYi8W8czff8ctXu/RPB25OJx5v2sCw9VirRogTo2zA== + dependencies: + "@swc/helpers" "^0.5.0" + +"@react-aria/menu@^3.16.0": + version "3.18.2" + resolved "https://registry.npmjs.org/@react-aria/menu/-/menu-3.18.2.tgz" + integrity sha512-90k+Ke1bhFWhR2zuRI6OwKWQrCpOD99n+9jhG96JZJZlNo5lB+5kS+ufG1LRv5GBnCug0ciLQmPMAfguVsCjEQ== + dependencies: + "@react-aria/focus" "^3.20.2" + "@react-aria/i18n" "^3.12.8" + "@react-aria/interactions" "^3.25.0" + "@react-aria/overlays" "^3.27.0" + "@react-aria/selection" "^3.24.0" + "@react-aria/utils" "^3.28.2" + "@react-stately/collections" "^3.12.3" + "@react-stately/menu" "^3.9.3" + "@react-stately/selection" "^3.20.1" + "@react-stately/tree" "^3.8.9" + "@react-types/button" "^3.12.0" + "@react-types/menu" "^3.10.0" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/menu@^3.18.2", "@react-aria/menu@^3.18.3": + version "3.18.3" + resolved "https://registry.npmjs.org/@react-aria/menu/-/menu-3.18.3.tgz" + integrity sha512-D0C4CM/QaxhCo2pLWNP+nfgnAeaSZWOdPMo9pnH/toRsoeTbnD6xO1hLhYsOx5ge+hrzjQvthjUrsjPB1AM/BQ== + dependencies: + "@react-aria/focus" "^3.20.3" + "@react-aria/i18n" "^3.12.9" + "@react-aria/interactions" "^3.25.1" + "@react-aria/overlays" "^3.27.1" + "@react-aria/selection" "^3.24.1" + "@react-aria/utils" "^3.29.0" + "@react-stately/collections" "^3.12.4" + "@react-stately/menu" "^3.9.4" + "@react-stately/selection" "^3.20.2" + "@react-stately/tree" "^3.8.10" + "@react-types/button" "^3.12.1" + "@react-types/menu" "^3.10.1" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/meter@^3.4.18": + version "3.4.22" + resolved "https://registry.npmjs.org/@react-aria/meter/-/meter-3.4.22.tgz" + integrity sha512-A/30vrtJO0xqctS/ngE1Lp/w3Aq3MPcpdRHU5E06EUYotzRzHFE9sNmezWslkZ3NfYwA/mxLvgmrsOJSR0Hx6A== + dependencies: + "@react-aria/progress" "^3.4.22" + "@react-types/meter" "^3.4.8" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/meter@^3.4.22", "@react-aria/meter@^3.4.23": + version "3.4.23" + resolved "https://registry.npmjs.org/@react-aria/meter/-/meter-3.4.23.tgz" + integrity sha512-FgmB/+cTE/sz+wTpTSmj9hFXw4nzfMUJGvXIePnF6f5Gx6J/U7aLEvNk7sXCp76apOu8k7ccma1nCsEvj74x7w== + dependencies: + "@react-aria/progress" "^3.4.23" + "@react-types/meter" "^3.4.9" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/numberfield@^3.11.13", "@react-aria/numberfield@^3.11.14": + version "3.11.14" + resolved "https://registry.npmjs.org/@react-aria/numberfield/-/numberfield-3.11.14.tgz" + integrity sha512-UvhPlRwVmbNEBBqfgL41P10H1jL4C7P2hWqsVw72tZQJl5k5ujeOzRWk8mkmg+D4FCZvv4iSPJhmyEP8HkgsWg== + dependencies: + "@react-aria/i18n" "^3.12.9" + "@react-aria/interactions" "^3.25.1" + "@react-aria/spinbutton" "^3.6.15" + "@react-aria/textfield" "^3.17.3" + "@react-aria/utils" "^3.29.0" + "@react-stately/form" "^3.1.4" + "@react-stately/numberfield" "^3.9.12" + "@react-types/button" "^3.12.1" + "@react-types/numberfield" "^3.8.11" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/numberfield@^3.11.9": + version "3.11.13" + resolved "https://registry.npmjs.org/@react-aria/numberfield/-/numberfield-3.11.13.tgz" + integrity sha512-F73BVdIRV8VvKl0omhGaf0E7mdJ7pdPjDP3wYNf410t55BXPxmndItUKpGfxSbl8k6ZYLvQyOqkD6oWSfZXpZw== + dependencies: + "@react-aria/i18n" "^3.12.8" + "@react-aria/interactions" "^3.25.0" + "@react-aria/spinbutton" "^3.6.14" + "@react-aria/textfield" "^3.17.2" + "@react-aria/utils" "^3.28.2" + "@react-stately/form" "^3.1.3" + "@react-stately/numberfield" "^3.9.11" + "@react-types/button" "^3.12.0" + "@react-types/numberfield" "^3.8.10" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/overlays@^3.24.0": + version "3.27.0" + resolved "https://registry.npmjs.org/@react-aria/overlays/-/overlays-3.27.0.tgz" + integrity sha512-2vZVgL7FrloN5Rh8sAhadGADJbuWg69DdSJB3fd2/h5VvcEhnIfNPu9Ma5XmdkApDoTboIEsKZ4QLYwRl98w6w== + dependencies: + "@react-aria/focus" "^3.20.2" + "@react-aria/i18n" "^3.12.8" + "@react-aria/interactions" "^3.25.0" + "@react-aria/ssr" "^3.9.8" + "@react-aria/utils" "^3.28.2" + "@react-aria/visually-hidden" "^3.8.22" + "@react-stately/overlays" "^3.6.15" + "@react-types/button" "^3.12.0" + "@react-types/overlays" "^3.8.14" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/overlays@^3.27.0", "@react-aria/overlays@^3.27.1": + version "3.27.1" + resolved "https://registry.npmjs.org/@react-aria/overlays/-/overlays-3.27.1.tgz" + integrity sha512-wepzwNLkgem6kVlLm6yk7zNIMAt0KPy8vAWlxdfpXWD/hBI30ULl71gL/BxRa5EYG1GMvlOwNti3whzy9lm3eQ== + dependencies: + "@react-aria/focus" "^3.20.3" + "@react-aria/i18n" "^3.12.9" + "@react-aria/interactions" "^3.25.1" + "@react-aria/ssr" "^3.9.8" + "@react-aria/utils" "^3.29.0" + "@react-aria/visually-hidden" "^3.8.23" + "@react-stately/overlays" "^3.6.16" + "@react-types/button" "^3.12.1" + "@react-types/overlays" "^3.8.15" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/progress@^3.4.18": + version "3.4.22" + resolved "https://registry.npmjs.org/@react-aria/progress/-/progress-3.4.22.tgz" + integrity sha512-wK2hath4C9HKgmjCH+iSrAs86sUKqqsYKbEKk9/Rj9rzXqHyaEK9EG0YZDnSjd8kX+N9hYcs5MfJl6AZMH4juQ== + dependencies: + "@react-aria/i18n" "^3.12.8" + "@react-aria/label" "^3.7.17" + "@react-aria/utils" "^3.28.2" + "@react-types/progress" "^3.5.11" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/progress@^3.4.22", "@react-aria/progress@^3.4.23": + version "3.4.23" + resolved "https://registry.npmjs.org/@react-aria/progress/-/progress-3.4.23.tgz" + integrity sha512-uSQBVY64k+CCey82U67KyWnjAfuuHF0fG6y76kIB8GHI8tGfd1NkXo4ioaxiY0SS+BYGqwqJYYMUzQMpOBTN1A== + dependencies: + "@react-aria/i18n" "^3.12.9" + "@react-aria/label" "^3.7.18" + "@react-aria/utils" "^3.29.0" + "@react-types/progress" "^3.5.12" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/radio@^3.10.10": + version "3.11.2" + resolved "https://registry.npmjs.org/@react-aria/radio/-/radio-3.11.2.tgz" + integrity sha512-6AFJHXMewJBgHNhqkN1qjgwwx6kmagwYD+3Z+hNK1UHTsKe1Uud5/IF7gPFCqlZeKxA+Lvn9gWiqJrQbtD2+wg== + dependencies: + "@react-aria/focus" "^3.20.2" + "@react-aria/form" "^3.0.15" + "@react-aria/i18n" "^3.12.8" + "@react-aria/interactions" "^3.25.0" + "@react-aria/label" "^3.7.17" + "@react-aria/utils" "^3.28.2" + "@react-stately/radio" "^3.10.12" + "@react-types/radio" "^3.8.8" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/radio@^3.11.2", "@react-aria/radio@^3.11.3": + version "3.11.3" + resolved "https://registry.npmjs.org/@react-aria/radio/-/radio-3.11.3.tgz" + integrity sha512-o10G8RUuHnAGZYzkc5PQw7mj4LMZqmGkoihDeHF2NDa9h44Ce5oeCPwRvCKYbumZDOyDY15ZIZhTUzjHt2w6fA== + dependencies: + "@react-aria/focus" "^3.20.3" + "@react-aria/form" "^3.0.16" + "@react-aria/i18n" "^3.12.9" + "@react-aria/interactions" "^3.25.1" + "@react-aria/label" "^3.7.18" + "@react-aria/utils" "^3.29.0" + "@react-stately/radio" "^3.10.13" + "@react-types/radio" "^3.8.9" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/searchfield@^3.7.11": + version "3.8.3" + resolved "https://registry.npmjs.org/@react-aria/searchfield/-/searchfield-3.8.3.tgz" + integrity sha512-t1DW3nUkPHyZhFhUbT+TdhvI8yZYvUPCuwl0FyraMRCQ4+ww5Ieu4n8JB9IGYmIUB/GWEbZlDHplu4s3efmliA== + dependencies: + "@react-aria/i18n" "^3.12.8" + "@react-aria/textfield" "^3.17.2" + "@react-aria/utils" "^3.28.2" + "@react-stately/searchfield" "^3.5.11" + "@react-types/button" "^3.12.0" + "@react-types/searchfield" "^3.6.1" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/searchfield@^3.8.3", "@react-aria/searchfield@^3.8.4": + version "3.8.4" + resolved "https://registry.npmjs.org/@react-aria/searchfield/-/searchfield-3.8.4.tgz" + integrity sha512-WnAvU9ct8+Asb8FFhGw6bggBmRaPe9qZPgYacenmRItwN+7UVTwEBVB9umO2bN3PLGm3CKgop10znd6ATiAbJA== + dependencies: + "@react-aria/i18n" "^3.12.9" + "@react-aria/textfield" "^3.17.3" + "@react-aria/utils" "^3.29.0" + "@react-stately/searchfield" "^3.5.12" + "@react-types/button" "^3.12.1" + "@react-types/searchfield" "^3.6.2" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/select@^3.15.0": + version "3.15.4" + resolved "https://registry.npmjs.org/@react-aria/select/-/select-3.15.4.tgz" + integrity sha512-CipqXgdOfWsiHw/chfqd8t9IQpvehP+3uKLJx3ic4Uyj+FT/SxVmmjX0gyvVbZd00ltFCMJYO2xYKQUlbW2AtQ== + dependencies: + "@react-aria/form" "^3.0.15" + "@react-aria/i18n" "^3.12.8" + "@react-aria/interactions" "^3.25.0" + "@react-aria/label" "^3.7.17" + "@react-aria/listbox" "^3.14.3" + "@react-aria/menu" "^3.18.2" + "@react-aria/selection" "^3.24.0" + "@react-aria/utils" "^3.28.2" + "@react-aria/visually-hidden" "^3.8.22" + "@react-stately/select" "^3.6.12" + "@react-types/button" "^3.12.0" + "@react-types/select" "^3.9.11" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/select@^3.15.4", "@react-aria/select@^3.15.5": + version "3.15.5" + resolved "https://registry.npmjs.org/@react-aria/select/-/select-3.15.5.tgz" + integrity sha512-2v8QmcPsZzlOjc/zsLbMcKeMKZoa+FZboxfjq4koUXtuaLhgopENChkfPLaXEGxqsejANs4dAoqiOiwwrGAaLQ== + dependencies: + "@react-aria/form" "^3.0.16" + "@react-aria/i18n" "^3.12.9" + "@react-aria/interactions" "^3.25.1" + "@react-aria/label" "^3.7.18" + "@react-aria/listbox" "^3.14.4" + "@react-aria/menu" "^3.18.3" + "@react-aria/selection" "^3.24.1" + "@react-aria/utils" "^3.29.0" + "@react-aria/visually-hidden" "^3.8.23" + "@react-stately/select" "^3.6.13" + "@react-types/button" "^3.12.1" + "@react-types/select" "^3.9.12" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/selection@^3.21.0": + version "3.24.0" + resolved "https://registry.npmjs.org/@react-aria/selection/-/selection-3.24.0.tgz" + integrity sha512-RfGXVc04zz41NVIW89/a3quURZ4LT/GJLkiajQK2VjhisidPdrAWkcfjjWJj0n+tm5gPWbi9Rs5R/Rc8mrvq8Q== + dependencies: + "@react-aria/focus" "^3.20.2" + "@react-aria/i18n" "^3.12.8" + "@react-aria/interactions" "^3.25.0" + "@react-aria/utils" "^3.28.2" + "@react-stately/selection" "^3.20.1" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/selection@^3.24.0", "@react-aria/selection@^3.24.1": + version "3.24.1" + resolved "https://registry.npmjs.org/@react-aria/selection/-/selection-3.24.1.tgz" + integrity sha512-nHUksgjg92iHgseH9L+krk9rX19xGJLTDeobKBX7eoAXQMqQjefu+oDwT0VYdI/qqNURNELE/KPZIVLC4PB81w== + dependencies: + "@react-aria/focus" "^3.20.3" + "@react-aria/i18n" "^3.12.9" + "@react-aria/interactions" "^3.25.1" + "@react-aria/utils" "^3.29.0" + "@react-stately/selection" "^3.20.2" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/separator@^3.4.4": + version "3.4.8" + resolved "https://registry.npmjs.org/@react-aria/separator/-/separator-3.4.8.tgz" + integrity sha512-ncuOSTBF/qbNumnW/IRz+xyr+Ud85eCF0Expw4XWhKjAZfzJd86MxPY5ZsxE7pYLOcRWdOSIH1/obwwwSz8ALQ== + dependencies: + "@react-aria/utils" "^3.28.2" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/separator@^3.4.8", "@react-aria/separator@^3.4.9": + version "3.4.9" + resolved "https://registry.npmjs.org/@react-aria/separator/-/separator-3.4.9.tgz" + integrity sha512-5ZKVQ/5I2+fw8WyVCQLGjQKsMKlTIieLPf8NvdC24a+pmiUluyUuqfPYdI8s6lcnjG0gbOzZB+jKvDRQbIvMPQ== + dependencies: + "@react-aria/utils" "^3.29.0" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/slider@^3.7.14": + version "3.7.18" + resolved "https://registry.npmjs.org/@react-aria/slider/-/slider-3.7.18.tgz" + integrity sha512-GBVv5Rpvj/6JH2LnF1zVAhBmxGiuq7R8Ekqyr5kBrCc2ToF3PrTjfGc/mlh0eEtbj+NvAcnlgTx1/qosYt1sGw== + dependencies: + "@react-aria/i18n" "^3.12.8" + "@react-aria/interactions" "^3.25.0" + "@react-aria/label" "^3.7.17" + "@react-aria/utils" "^3.28.2" + "@react-stately/slider" "^3.6.3" + "@react-types/shared" "^3.29.0" + "@react-types/slider" "^3.7.10" + "@swc/helpers" "^0.5.0" + +"@react-aria/slider@^3.7.18", "@react-aria/slider@^3.7.19": + version "3.7.19" + resolved "https://registry.npmjs.org/@react-aria/slider/-/slider-3.7.19.tgz" + integrity sha512-GONrMMz9zsx0ySbUTebWdqRjAuu6EEW+lLf3qUzcqkIYR8QZVTS8RLPt7FmGHKCTDIaBs8D2yv9puIfKAo1QAA== + dependencies: + "@react-aria/i18n" "^3.12.9" + "@react-aria/interactions" "^3.25.1" + "@react-aria/label" "^3.7.18" + "@react-aria/utils" "^3.29.0" + "@react-stately/slider" "^3.6.4" + "@react-types/shared" "^3.29.1" + "@react-types/slider" "^3.7.11" + "@swc/helpers" "^0.5.0" + +"@react-aria/spinbutton@^3.6.10": + version "3.6.14" + resolved "https://registry.npmjs.org/@react-aria/spinbutton/-/spinbutton-3.6.14.tgz" + integrity sha512-oSKe9p0Q/7W39eXRnLxlwJG5dQo4ffosRT3u2AtOcFkk2Zzj+tSQFzHQ4202nrWdzRnQ2KLTgUUNnUvXf0BJcg== + dependencies: + "@react-aria/i18n" "^3.12.8" + "@react-aria/live-announcer" "^3.4.2" + "@react-aria/utils" "^3.28.2" + "@react-types/button" "^3.12.0" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/spinbutton@^3.6.14", "@react-aria/spinbutton@^3.6.15": + version "3.6.15" + resolved "https://registry.npmjs.org/@react-aria/spinbutton/-/spinbutton-3.6.15.tgz" + integrity sha512-dVKaRgrSU2utxCd4kqAA8BPrC1PVI1eiJ8gvlVbg25LbwK4dg1WPXQUK+80TbrJc9mOEooPiJvzw59IoQLMNRg== + dependencies: + "@react-aria/i18n" "^3.12.9" + "@react-aria/live-announcer" "^3.4.2" + "@react-aria/utils" "^3.29.0" + "@react-types/button" "^3.12.1" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/ssr@^3.9.7", "@react-aria/ssr@^3.9.8": + version "3.9.8" + resolved "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.8.tgz" + integrity sha512-lQDE/c9uTfBSDOjaZUJS8xP2jCKVk4zjQeIlCH90xaLhHDgbpCdns3xvFpJJujfj3nI4Ll9K7A+ONUBDCASOuw== + dependencies: + "@swc/helpers" "^0.5.0" + +"@react-aria/switch@^3.6.10": + version "3.7.2" + resolved "https://registry.npmjs.org/@react-aria/switch/-/switch-3.7.2.tgz" + integrity sha512-vaREbp1gFjv+jEMXoXpNK7JYFO/jhwnSYAwEINNWnwf54IGeHvTPaB2NwolYSFvP4HAj8TKYbGFUSz7RKLhLgw== + dependencies: + "@react-aria/toggle" "^3.11.2" + "@react-stately/toggle" "^3.8.3" + "@react-types/shared" "^3.29.0" + "@react-types/switch" "^3.5.10" + "@swc/helpers" "^0.5.0" + +"@react-aria/switch@^3.7.2", "@react-aria/switch@^3.7.3": + version "3.7.3" + resolved "https://registry.npmjs.org/@react-aria/switch/-/switch-3.7.3.tgz" + integrity sha512-tFdJmcHaLgW23cS2R713vcJdVbsjDTRk8OLdG/sMziPBY3C00/exuSIb57xTS7KrE0hBYfnLJQTcmDNqdM8+9Q== + dependencies: + "@react-aria/toggle" "^3.11.3" + "@react-stately/toggle" "^3.8.4" + "@react-types/shared" "^3.29.1" + "@react-types/switch" "^3.5.11" + "@swc/helpers" "^0.5.0" + +"@react-aria/table@^3.16.0": + version "3.17.2" + resolved "https://registry.npmjs.org/@react-aria/table/-/table-3.17.2.tgz" + integrity sha512-wsF3JqiAKcol1sfeNqTxyzH6+nxu0sAfyuh+XQfp1tvSGx15NifYeNKovNX4EPpUVkAI7jL5Le+eYeYYGELfnw== + dependencies: + "@react-aria/focus" "^3.20.2" + "@react-aria/grid" "^3.13.0" + "@react-aria/i18n" "^3.12.8" + "@react-aria/interactions" "^3.25.0" + "@react-aria/live-announcer" "^3.4.2" + "@react-aria/utils" "^3.28.2" + "@react-aria/visually-hidden" "^3.8.22" + "@react-stately/collections" "^3.12.3" + "@react-stately/flags" "^3.1.1" + "@react-stately/table" "^3.14.1" + "@react-types/checkbox" "^3.9.3" + "@react-types/grid" "^3.3.1" + "@react-types/shared" "^3.29.0" + "@react-types/table" "^3.12.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/table@^3.17.2", "@react-aria/table@^3.17.3": + version "3.17.3" + resolved "https://registry.npmjs.org/@react-aria/table/-/table-3.17.3.tgz" + integrity sha512-hs3akyNMeeAPIfa+YKMxJyupSjywW5OGzJtOw/Z0j6pV8KXSeMEXNYkSuJY+m5Q1mdunoiiogs0kE3B0r2izQA== + dependencies: + "@react-aria/focus" "^3.20.3" + "@react-aria/grid" "^3.14.0" + "@react-aria/i18n" "^3.12.9" + "@react-aria/interactions" "^3.25.1" + "@react-aria/live-announcer" "^3.4.2" + "@react-aria/utils" "^3.29.0" + "@react-aria/visually-hidden" "^3.8.23" + "@react-stately/collections" "^3.12.4" + "@react-stately/flags" "^3.1.1" + "@react-stately/table" "^3.14.2" + "@react-types/checkbox" "^3.9.4" + "@react-types/grid" "^3.3.2" + "@react-types/shared" "^3.29.1" + "@react-types/table" "^3.13.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/tabs@^3.10.2", "@react-aria/tabs@^3.10.3": + version "3.10.3" + resolved "https://registry.npmjs.org/@react-aria/tabs/-/tabs-3.10.3.tgz" + integrity sha512-TYfwaRrI0mQMefmoHeTKXdczpb53qpPr+3nnveGl+BocG94wmjIqK6kncboVbPdykgQCIAMd2d9GFpK01+zXrA== + dependencies: + "@react-aria/focus" "^3.20.3" + "@react-aria/i18n" "^3.12.9" + "@react-aria/selection" "^3.24.1" + "@react-aria/utils" "^3.29.0" + "@react-stately/tabs" "^3.8.2" + "@react-types/shared" "^3.29.1" + "@react-types/tabs" "^3.3.15" + "@swc/helpers" "^0.5.0" + +"@react-aria/tabs@^3.9.8": + version "3.10.2" + resolved "https://registry.npmjs.org/@react-aria/tabs/-/tabs-3.10.2.tgz" + integrity sha512-rpEgh//Gnew3le49tQVFOQ6ZyacJdaNUDXHt0ocguXb+2UrKtH54M8oIAE7E8KaB1puQlFXRs+Rjlr1rOlmjEQ== + dependencies: + "@react-aria/focus" "^3.20.2" + "@react-aria/i18n" "^3.12.8" + "@react-aria/selection" "^3.24.0" + "@react-aria/utils" "^3.28.2" + "@react-stately/tabs" "^3.8.1" + "@react-types/shared" "^3.29.0" + "@react-types/tabs" "^3.3.14" + "@swc/helpers" "^0.5.0" + +"@react-aria/tag@^3.4.8": + version "3.5.2" + resolved "https://registry.npmjs.org/@react-aria/tag/-/tag-3.5.2.tgz" + integrity sha512-xZ5Df0x+xcDg6UTDvnjP4pu+XrmYVaYcqzF7RGoCD1KyRCHU5Czg9p+888NB0K+vnJHfNsQh6rmMhDUydXu9eg== + dependencies: + "@react-aria/gridlist" "^3.12.0" + "@react-aria/i18n" "^3.12.8" + "@react-aria/interactions" "^3.25.0" + "@react-aria/label" "^3.7.17" + "@react-aria/selection" "^3.24.0" + "@react-aria/utils" "^3.28.2" + "@react-stately/list" "^3.12.1" + "@react-types/button" "^3.12.0" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/tag@^3.5.2", "@react-aria/tag@^3.6.0": + version "3.6.0" + resolved "https://registry.npmjs.org/@react-aria/tag/-/tag-3.6.0.tgz" + integrity sha512-OkLyFYTFVUYB339eugw2r6vIcrWq47O15x4sKNkDUo6YBx9ci9tdoib4DlzwuiiKVr/vmw1WMow6VK4zOtuLng== + dependencies: + "@react-aria/gridlist" "^3.13.0" + "@react-aria/i18n" "^3.12.9" + "@react-aria/interactions" "^3.25.1" + "@react-aria/label" "^3.7.18" + "@react-aria/selection" "^3.24.1" + "@react-aria/utils" "^3.29.0" + "@react-stately/list" "^3.12.2" + "@react-types/button" "^3.12.1" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/textfield@^3.15.0": + version "3.17.2" + resolved "https://registry.npmjs.org/@react-aria/textfield/-/textfield-3.17.2.tgz" + integrity sha512-4KINB0HueYUHUgvi/ThTP27hu4Mv5ujG55pH3dmSRD4Olu/MRy1m/Psq72o8LTf4bTOM9ZP1rKccUg6xfaMidA== + dependencies: + "@react-aria/form" "^3.0.15" + "@react-aria/interactions" "^3.25.0" + "@react-aria/label" "^3.7.17" + "@react-aria/utils" "^3.28.2" + "@react-stately/form" "^3.1.3" + "@react-stately/utils" "^3.10.6" + "@react-types/shared" "^3.29.0" + "@react-types/textfield" "^3.12.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/textfield@^3.17.2", "@react-aria/textfield@^3.17.3": + version "3.17.3" + resolved "https://registry.npmjs.org/@react-aria/textfield/-/textfield-3.17.3.tgz" + integrity sha512-p/Z0fyE0CnzIrnCf42gxeSCNYon7//XkcbPwUS4U9dz2VLk2GnEn9NZXPYgTp+08ebQEn0pB1QIchX79yFEguw== + dependencies: + "@react-aria/form" "^3.0.16" + "@react-aria/interactions" "^3.25.1" + "@react-aria/label" "^3.7.18" + "@react-aria/utils" "^3.29.0" + "@react-stately/form" "^3.1.4" + "@react-stately/utils" "^3.10.6" + "@react-types/shared" "^3.29.1" + "@react-types/textfield" "^3.12.2" + "@swc/helpers" "^0.5.0" + +"@react-aria/toast@^3.0.2", "@react-aria/toast@^3.0.3": + version "3.0.3" + resolved "https://registry.npmjs.org/@react-aria/toast/-/toast-3.0.3.tgz" + integrity sha512-7HWTKIVwS1JFC8//BQbRtGFaAdq4SljvI3yI5amLr90CyVM0sugTtcSX9a8BPnp1j9ao+6bmOi/wrV48mze1PA== + dependencies: + "@react-aria/i18n" "^3.12.9" + "@react-aria/interactions" "^3.25.1" + "@react-aria/landmark" "^3.0.3" + "@react-aria/utils" "^3.29.0" + "@react-stately/toast" "^3.1.0" + "@react-types/button" "^3.12.1" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/toggle@^3.11.2": + version "3.11.2" + resolved "https://registry.npmjs.org/@react-aria/toggle/-/toggle-3.11.2.tgz" + integrity sha512-JOg8yYYCjLDnEpuggPo9GyXFaT/B238d3R8i/xQ6KLelpi3fXdJuZlFD6n9NQp3DJbE8Wj+wM5/VFFAi3cISpw== + dependencies: + "@react-aria/interactions" "^3.25.0" + "@react-aria/utils" "^3.28.2" + "@react-stately/toggle" "^3.8.3" + "@react-types/checkbox" "^3.9.3" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/toggle@^3.11.3": + version "3.11.3" + resolved "https://registry.npmjs.org/@react-aria/toggle/-/toggle-3.11.3.tgz" + integrity sha512-S6ShToNR6TukRJh8qDdyl9b2Bcsx43eurUB5USANn4ycPov8+bIxQnxiknjssZx7jD8vX4jruuNh7BjFbNsGFw== + dependencies: + "@react-aria/interactions" "^3.25.1" + "@react-aria/utils" "^3.29.0" + "@react-stately/toggle" "^3.8.4" + "@react-types/checkbox" "^3.9.4" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/toolbar@3.0.0-beta.11": + version "3.0.0-beta.11" + resolved "https://registry.npmjs.org/@react-aria/toolbar/-/toolbar-3.0.0-beta.11.tgz" + integrity sha512-LM3jTRFNDgoEpoL568WaiuqiVM7eynSQLJis1hV0vlVnhTd7M7kzt7zoOjzxVb5Uapz02uCp1Fsm4wQMz09qwQ== + dependencies: + "@react-aria/focus" "^3.19.0" + "@react-aria/i18n" "^3.12.4" + "@react-aria/utils" "^3.26.0" + "@react-types/shared" "^3.26.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/toolbar@3.0.0-beta.15": + version "3.0.0-beta.15" + resolved "https://registry.npmjs.org/@react-aria/toolbar/-/toolbar-3.0.0-beta.15.tgz" + integrity sha512-PNGpNIKIsCW8rxI9XXSADlLrSpikILJKKECyTRw9KwvXDRc44pezvdjGHCNinQcKsQoy5BtkK5cTSAyVqzzTXQ== + dependencies: + "@react-aria/focus" "^3.20.2" + "@react-aria/i18n" "^3.12.8" + "@react-aria/utils" "^3.28.2" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/toolbar@3.0.0-beta.16": + version "3.0.0-beta.16" + resolved "https://registry.npmjs.org/@react-aria/toolbar/-/toolbar-3.0.0-beta.16.tgz" + integrity sha512-TnNvtxADalMzs9Et51hWPpGyiHr1dt++UYR7pIo1H7vO+HwXl6uH4HxbFDS5CyV69j2cQlcGrkj13LoWFkBECw== + dependencies: + "@react-aria/focus" "^3.20.3" + "@react-aria/i18n" "^3.12.9" + "@react-aria/utils" "^3.29.0" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/tooltip@^3.7.10": + version "3.8.2" + resolved "https://registry.npmjs.org/@react-aria/tooltip/-/tooltip-3.8.2.tgz" + integrity sha512-ctVTgh1LXvmr1ve3ehAWfvlJR7nHYZeqhl/g1qnA+983LQtc1IF9MraCs92g0m7KpBwCihuA+aYwTPsUHfKfXg== + dependencies: + "@react-aria/interactions" "^3.25.0" + "@react-aria/utils" "^3.28.2" + "@react-stately/tooltip" "^3.5.3" + "@react-types/shared" "^3.29.0" + "@react-types/tooltip" "^3.4.16" + "@swc/helpers" "^0.5.0" + +"@react-aria/tooltip@^3.8.2", "@react-aria/tooltip@^3.8.3": + version "3.8.3" + resolved "https://registry.npmjs.org/@react-aria/tooltip/-/tooltip-3.8.3.tgz" + integrity sha512-8JHRqffH5vUw7og6mlCRzb4h95/R5RpOxGFfEGw7aami14XMo6tZg7wMgwDUAEiVqNerRWYaw+tk7nCUQXo1Sg== + dependencies: + "@react-aria/interactions" "^3.25.1" + "@react-aria/utils" "^3.29.0" + "@react-stately/tooltip" "^3.5.4" + "@react-types/shared" "^3.29.1" + "@react-types/tooltip" "^3.4.17" + "@swc/helpers" "^0.5.0" + +"@react-aria/tree@3.0.0-beta.2": + version "3.0.0-beta.2" + resolved "https://registry.npmjs.org/@react-aria/tree/-/tree-3.0.0-beta.2.tgz" + integrity sha512-lH3hVl2VgG3YLN+ee1zQzm+2F+BGLd/HBhfMYPuI3IjHvDb+m+jCJXHdBOGrfG2Qydk2LYheqX8QXCluulu0qQ== + dependencies: + "@react-aria/gridlist" "^3.10.0" + "@react-aria/i18n" "^3.12.4" + "@react-aria/selection" "^3.21.0" + "@react-aria/utils" "^3.26.0" + "@react-stately/tree" "^3.8.6" + "@react-types/button" "^3.10.1" + "@react-types/shared" "^3.26.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/tree@^3.0.2", "@react-aria/tree@^3.0.3": + version "3.0.3" + resolved "https://registry.npmjs.org/@react-aria/tree/-/tree-3.0.3.tgz" + integrity sha512-kdA0CCUD8luCrXZFo0rX1c0LI8jovYMuWsPiI5OpmiEKGA5HaVFFW/H9t/XSYdVc/JO08zbeZ/WacTusKeOT3Q== + dependencies: + "@react-aria/gridlist" "^3.13.0" + "@react-aria/i18n" "^3.12.9" + "@react-aria/selection" "^3.24.1" + "@react-aria/utils" "^3.29.0" + "@react-stately/tree" "^3.8.10" + "@react-types/button" "^3.12.1" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/utils@^3.26.0": + version "3.28.2" + resolved "https://registry.npmjs.org/@react-aria/utils/-/utils-3.28.2.tgz" + integrity sha512-J8CcLbvnQgiBn54eeEvQQbIOfBF3A1QizxMw9P4cl9MkeR03ug7RnjTIdJY/n2p7t59kLeAB3tqiczhcj+Oi5w== + dependencies: + "@react-aria/ssr" "^3.9.8" + "@react-stately/flags" "^3.1.1" + "@react-stately/utils" "^3.10.6" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + clsx "^2.0.0" + +"@react-aria/utils@^3.28.2", "@react-aria/utils@^3.29.0": + version "3.29.0" + resolved "https://registry.npmjs.org/@react-aria/utils/-/utils-3.29.0.tgz" + integrity sha512-jSOrZimCuT1iKNVlhjIxDkAhgF7HSp3pqyT6qjg/ZoA0wfqCi/okmrMPiWSAKBnkgX93N8GYTLT3CIEO6WZe9Q== + dependencies: + "@react-aria/ssr" "^3.9.8" + "@react-stately/flags" "^3.1.1" + "@react-stately/utils" "^3.10.6" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + clsx "^2.0.0" + +"@react-aria/virtualizer@^4.1.0": + version "4.1.4" + resolved "https://registry.npmjs.org/@react-aria/virtualizer/-/virtualizer-4.1.4.tgz" + integrity sha512-SBKD2K+kBc3aLMVEqnBXjpqLhUSyvoi1ubSgUS5KMIqgyn44OWn5zKTsj9SIPZot6buSlgV2700TIWDhEJzWlw== + dependencies: + "@react-aria/i18n" "^3.12.8" + "@react-aria/interactions" "^3.25.0" + "@react-aria/utils" "^3.28.2" + "@react-stately/virtualizer" "^4.3.2" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/virtualizer@^4.1.4", "@react-aria/virtualizer@^4.1.5": + version "4.1.5" + resolved "https://registry.npmjs.org/@react-aria/virtualizer/-/virtualizer-4.1.5.tgz" + integrity sha512-Z5+Zr54HCBqycIzZuHohS25dOJ7p8sdNDjAYvW33Uq8nudTvSC5JmV/5kZVN11j5kVYXa7maRnFQlDx941sygw== + dependencies: + "@react-aria/i18n" "^3.12.9" + "@react-aria/interactions" "^3.25.1" + "@react-aria/utils" "^3.29.0" + "@react-stately/virtualizer" "^4.4.0" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-aria/visually-hidden@^3.8.18": + version "3.8.22" + resolved "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.8.22.tgz" + integrity sha512-EO3R8YTKZ7HkLl9k1Y2uBKYBgpJagth4/4W7mfpJZE24A3fQnCP8zx1sweXiAm0mirR4J6tNaK7Ia8ssP5TpOw== + dependencies: + "@react-aria/interactions" "^3.25.0" + "@react-aria/utils" "^3.28.2" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/visually-hidden@^3.8.22", "@react-aria/visually-hidden@^3.8.23": + version "3.8.23" + resolved "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.8.23.tgz" + integrity sha512-D37GHtAcxCck8BtCiGTNDniGqtldJuN0cRlW1PJ684zM4CdmkSPqKbt5IUKUfqheS9Vt7HxYsj1VREDW+0kaGA== + dependencies: + "@react-aria/interactions" "^3.25.1" + "@react-aria/utils" "^3.29.0" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-dnd/asap@^4.0.0": + version "4.0.1" + resolved "https://registry.npmjs.org/@react-dnd/asap/-/asap-4.0.1.tgz" + integrity sha512-kLy0PJDDwvwwTXxqTFNAAllPHD73AycE9ypWeln/IguoGBEbvFcPDbCV03G52bEcC5E+YgupBE0VzHGdC8SIXg== + +"@react-dnd/invariant@^2.0.0": + version "2.0.0" + resolved "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-2.0.0.tgz" + integrity sha512-xL4RCQBCBDJ+GRwKTFhGUW8GXa4yoDfJrPbLblc3U09ciS+9ZJXJ3Qrcs/x2IODOdIE5kQxvMmE2UKyqUictUw== + +"@react-dnd/shallowequal@^2.0.0": + version "2.0.0" + resolved "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-2.0.0.tgz" + integrity sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg== + +"@react-pdf/fns@3.1.2": + version "3.1.2" + resolved "https://registry.npmjs.org/@react-pdf/fns/-/fns-3.1.2.tgz" + integrity sha512-qTKGUf0iAMGg2+OsUcp9ffKnKi41RukM/zYIWMDJ4hRVYSr89Q7e3wSDW/Koqx3ea3Uy/z3h2y3wPX6Bdfxk6g== + +"@react-pdf/font@^4.0.2": + version "4.0.2" + resolved "https://registry.npmjs.org/@react-pdf/font/-/font-4.0.2.tgz" + integrity sha512-/dAWu7Y2RD1RxarDZ9SkYPHgBYOhmcDnet4W/qN/m8k+A2Hr3ja54GymSR7GGxWBtxjKtNauVKrTa9LS1n8WUw== + dependencies: + "@react-pdf/pdfkit" "^4.0.3" + "@react-pdf/types" "^2.9.0" + fontkit "^2.0.2" + is-url "^1.2.4" + +"@react-pdf/image@^3.0.3": + version "3.0.3" + resolved "https://registry.npmjs.org/@react-pdf/image/-/image-3.0.3.tgz" + integrity sha512-lvP5ryzYM3wpbO9bvqLZYwEr5XBDX9jcaRICvtnoRqdJOo7PRrMnmB4MMScyb+Xw10mGeIubZAAomNAG5ONQZQ== + dependencies: + "@react-pdf/png-js" "^3.0.0" + jay-peg "^1.1.1" + +"@react-pdf/layout@^4.4.0": + version "4.4.0" + resolved "https://registry.npmjs.org/@react-pdf/layout/-/layout-4.4.0.tgz" + integrity sha512-Aq+Cc6JYausWLoks2FvHe3PwK9cTuvksB2uJ0AnkKJEUtQbvCq8eCRb1bjbbwIji9OzFRTTzZij7LzkpKHjIeA== + dependencies: + "@react-pdf/fns" "3.1.2" + "@react-pdf/image" "^3.0.3" + "@react-pdf/primitives" "^4.1.1" + "@react-pdf/stylesheet" "^6.1.0" + "@react-pdf/textkit" "^6.0.0" + "@react-pdf/types" "^2.9.0" + emoji-regex "^10.3.0" + queue "^6.0.1" + yoga-layout "^3.2.1" + +"@react-pdf/pdfkit@^4.0.3": + version "4.0.3" + resolved "https://registry.npmjs.org/@react-pdf/pdfkit/-/pdfkit-4.0.3.tgz" + integrity sha512-k+Lsuq8vTwWsCqTp+CCB4+2N+sOTFrzwGA7aw3H9ix/PDWR9QksbmNg0YkzGbLAPI6CeawmiLHcf4trZ5ecLPQ== + dependencies: + "@babel/runtime" "^7.20.13" + "@react-pdf/png-js" "^3.0.0" + browserify-zlib "^0.2.0" + crypto-js "^4.2.0" + fontkit "^2.0.2" + jay-peg "^1.1.1" + linebreak "^1.1.0" + vite-compatible-readable-stream "^3.6.1" + +"@react-pdf/png-js@^3.0.0": + version "3.0.0" + resolved "https://registry.npmjs.org/@react-pdf/png-js/-/png-js-3.0.0.tgz" + integrity sha512-eSJnEItZ37WPt6Qv5pncQDxLJRK15eaRwPT+gZoujP548CodenOVp49GST8XJvKMFt9YqIBzGBV/j9AgrOQzVA== + dependencies: + browserify-zlib "^0.2.0" + +"@react-pdf/primitives@^4.1.1": + version "4.1.1" + resolved "https://registry.npmjs.org/@react-pdf/primitives/-/primitives-4.1.1.tgz" + integrity sha512-IuhxYls1luJb7NUWy6q5avb1XrNaVj9bTNI40U9qGRuS6n7Hje/8H8Qi99Z9UKFV74bBP3DOf3L1wV2qZVgVrQ== + +"@react-pdf/reconciler@^1.1.4": + version "1.1.4" + resolved "https://registry.npmjs.org/@react-pdf/reconciler/-/reconciler-1.1.4.tgz" + integrity sha512-oTQDiR/t4Z/Guxac88IavpU2UgN7eR0RMI9DRKvKnvPz2DUasGjXfChAdMqDNmJJxxV26mMy9xQOUV2UU5/okg== + dependencies: + object-assign "^4.1.1" + scheduler "0.25.0-rc-603e6108-20241029" + +"@react-pdf/render@^4.3.0": + version "4.3.0" + resolved "https://registry.npmjs.org/@react-pdf/render/-/render-4.3.0.tgz" + integrity sha512-MdWfWaqO6d7SZD75TZ2z5L35V+cHpyA43YNRlJNG0RJ7/MeVGDQv12y/BXOJgonZKkeEGdzM3EpAt9/g4E22WA== + dependencies: + "@babel/runtime" "^7.20.13" + "@react-pdf/fns" "3.1.2" + "@react-pdf/primitives" "^4.1.1" + "@react-pdf/textkit" "^6.0.0" + "@react-pdf/types" "^2.9.0" + abs-svg-path "^0.1.1" + color-string "^1.9.1" + normalize-svg-path "^1.1.0" + parse-svg-path "^0.1.2" + svg-arc-to-cubic-bezier "^3.2.0" + +"@react-pdf/renderer@4.3.0": + version "4.3.0" + resolved "https://registry.npmjs.org/@react-pdf/renderer/-/renderer-4.3.0.tgz" + integrity sha512-28gpA69fU9ZQrDzmd5xMJa1bDf8t0PT3ApUKBl2PUpoE/x4JlvCB5X66nMXrfFrgF2EZrA72zWQAkvbg7TE8zw== + dependencies: + "@babel/runtime" "^7.20.13" + "@react-pdf/fns" "3.1.2" + "@react-pdf/font" "^4.0.2" + "@react-pdf/layout" "^4.4.0" + "@react-pdf/pdfkit" "^4.0.3" + "@react-pdf/primitives" "^4.1.1" + "@react-pdf/reconciler" "^1.1.4" + "@react-pdf/render" "^4.3.0" + "@react-pdf/types" "^2.9.0" + events "^3.3.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + queue "^6.0.1" + +"@react-pdf/stylesheet@^6.1.0": + version "6.1.0" + resolved "https://registry.npmjs.org/@react-pdf/stylesheet/-/stylesheet-6.1.0.tgz" + integrity sha512-BGZ2sYNUp38VJUegjva/jsri3iiRGnVNjWI+G9dTwAvLNOmwFvSJzqaCsEnqQ/DW5mrTBk/577FhDY7pv6AidA== + dependencies: + "@react-pdf/fns" "3.1.2" + "@react-pdf/types" "^2.9.0" + color-string "^1.9.1" + hsl-to-hex "^1.0.0" + media-engine "^1.0.3" + postcss-value-parser "^4.1.0" + +"@react-pdf/textkit@^6.0.0": + version "6.0.0" + resolved "https://registry.npmjs.org/@react-pdf/textkit/-/textkit-6.0.0.tgz" + integrity sha512-fDt19KWaJRK/n2AaFoVm31hgGmpygmTV7LsHGJNGZkgzXcFyLsx+XUl63DTDPH3iqxj3xUX128t104GtOz8tTw== + dependencies: + "@react-pdf/fns" "3.1.2" + bidi-js "^1.0.2" + hyphen "^1.6.4" + unicode-properties "^1.4.1" + +"@react-pdf/types@^2.9.0": + version "2.9.0" + resolved "https://registry.npmjs.org/@react-pdf/types/-/types-2.9.0.tgz" + integrity sha512-ckj80vZLlvl9oYrQ4tovEaqKWP3O06Eb1D48/jQWbdwz1Yh7Y9v1cEmwlP8ET+a1Whp8xfdM0xduMexkuPANCQ== + dependencies: + "@react-pdf/font" "^4.0.2" + "@react-pdf/primitives" "^4.1.1" + "@react-pdf/stylesheet" "^6.1.0" + +"@react-stately/autocomplete@3.0.0-beta.1": + version "3.0.0-beta.1" + resolved "https://registry.npmjs.org/@react-stately/autocomplete/-/autocomplete-3.0.0-beta.1.tgz" + integrity sha512-ohs6QOtJouQ+Y1+zRKiCzv57QogSTRuOA1QfrnIS1YPwKO1EDQXSqFkq2htK5+bN9GCm94yo6r4iX++SZKmLXA== + dependencies: + "@react-stately/utils" "^3.10.6" + "@swc/helpers" "^0.5.0" + +"@react-stately/calendar@3.6.0", "@react-stately/calendar@^3.6.0": + version "3.6.0" + resolved "https://registry.npmjs.org/@react-stately/calendar/-/calendar-3.6.0.tgz" + integrity sha512-GqUtOtGnwWjtNrJud8nY/ywI4VBP5byToNVRTnxbMl+gYO1Qe/uc5NG7zjwMxhb2kqSBHZFdkF0DXVqG2Ul+BA== + dependencies: + "@internationalized/date" "^3.6.0" + "@react-stately/utils" "^3.10.5" + "@react-types/calendar" "^3.5.0" + "@react-types/shared" "^3.26.0" + "@swc/helpers" "^0.5.0" + +"@react-stately/calendar@3.8.0", "@react-stately/calendar@^3.8.0": + version "3.8.0" + resolved "https://registry.npmjs.org/@react-stately/calendar/-/calendar-3.8.0.tgz" + integrity sha512-YAuJiR9EtVThX91gU2ay/6YgPe0LvZWEssu4BS0Atnwk5cAo32gvF5FMta9ztH1LIULdZFaypU/C1mvnayMf+Q== + dependencies: + "@internationalized/date" "^3.8.0" + "@react-stately/utils" "^3.10.6" + "@react-types/calendar" "^3.7.0" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-stately/calendar@^3.8.1": + version "3.8.1" + resolved "https://registry.npmjs.org/@react-stately/calendar/-/calendar-3.8.1.tgz" + integrity sha512-pTPRmPRD/0JeKhCRvXhVIH/yBimtIHnZGUxH12dcTl3MLxjXQDTn6/LWK0s4rzJcjsC+EzGUCVBBXgESb7PUlw== + dependencies: + "@internationalized/date" "^3.8.1" + "@react-stately/utils" "^3.10.6" + "@react-types/calendar" "^3.7.1" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-stately/checkbox@^3.6.10", "@react-stately/checkbox@^3.6.13", "@react-stately/checkbox@^3.6.14": + version "3.6.14" + resolved "https://registry.npmjs.org/@react-stately/checkbox/-/checkbox-3.6.14.tgz" + integrity sha512-eGl0GP/F/nUrA33gDCYikyXK+Yer7sFOx8T4EU2AF4E8n1VQIRiVNaxDg7Ar6L3CMKor01urppFHFJsBUnSgyw== + dependencies: + "@react-stately/form" "^3.1.4" + "@react-stately/utils" "^3.10.6" + "@react-types/checkbox" "^3.9.4" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-stately/collections@^3.12.0", "@react-stately/collections@^3.12.3", "@react-stately/collections@^3.12.4": + version "3.12.4" + resolved "https://registry.npmjs.org/@react-stately/collections/-/collections-3.12.4.tgz" + integrity sha512-H+47fRkwYX2/BdSA+NLTzbR+8QclZXyBgC7tHH3dzljyxNimhrMDnbmk520nvGCebNf3nuxtFHq9iVTLpazSVA== + dependencies: + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-stately/color@^3.8.1", "@react-stately/color@^3.8.4", "@react-stately/color@^3.8.5": + version "3.8.5" + resolved "https://registry.npmjs.org/@react-stately/color/-/color-3.8.5.tgz" + integrity sha512-yi1MQAbYuAYKu0AtMO+mWQWlWk6OzGMa9j4PGtQN2PI5Uv1NylWOvdquxbUJ4GUAuSYNopYG8Ci9MZMwtito8w== + dependencies: + "@internationalized/number" "^3.6.2" + "@internationalized/string" "^3.2.6" + "@react-stately/form" "^3.1.4" + "@react-stately/numberfield" "^3.9.12" + "@react-stately/slider" "^3.6.4" + "@react-stately/utils" "^3.10.6" + "@react-types/color" "^3.0.5" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-stately/combobox@^3.10.1", "@react-stately/combobox@^3.10.4", "@react-stately/combobox@^3.10.5": + version "3.10.5" + resolved "https://registry.npmjs.org/@react-stately/combobox/-/combobox-3.10.5.tgz" + integrity sha512-27SkClMqbMAKuVnmXhYzYisbLfzV7MO/DEiqWO4/3l+PZ+whL7Wi/Ek7Wqlfluid/y4pN4EkHCKNt4HJ2mhORQ== + dependencies: + "@react-stately/collections" "^3.12.4" + "@react-stately/form" "^3.1.4" + "@react-stately/list" "^3.12.2" + "@react-stately/overlays" "^3.6.16" + "@react-stately/select" "^3.6.13" + "@react-stately/utils" "^3.10.6" + "@react-types/combobox" "^3.13.5" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-stately/data@^3.12.0", "@react-stately/data@^3.12.3", "@react-stately/data@^3.13.0": + version "3.13.0" + resolved "https://registry.npmjs.org/@react-stately/data/-/data-3.13.0.tgz" + integrity sha512-7LYPxVbWB6tvmLYKO19H5G5YtXV6eKCSXisOUiL9fVnOcGOPDK5z310sj9TP5vaX7zVPtwy0lDBUrZuRfhvQIQ== + dependencies: + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-stately/datepicker@3.11.0", "@react-stately/datepicker@^3.11.0": + version "3.11.0" + resolved "https://registry.npmjs.org/@react-stately/datepicker/-/datepicker-3.11.0.tgz" + integrity sha512-d9MJF34A0VrhL5y5S8mAISA8uwfNCQKmR2k4KoQJm3De1J8SQeNzSjLviAwh1faDow6FXGlA6tVbTrHyDcBgBg== + dependencies: + "@internationalized/date" "^3.6.0" + "@internationalized/string" "^3.2.5" + "@react-stately/form" "^3.1.0" + "@react-stately/overlays" "^3.6.12" + "@react-stately/utils" "^3.10.5" + "@react-types/datepicker" "^3.9.0" + "@react-types/shared" "^3.26.0" + "@swc/helpers" "^0.5.0" + +"@react-stately/datepicker@3.14.0", "@react-stately/datepicker@^3.14.0": + version "3.14.0" + resolved "https://registry.npmjs.org/@react-stately/datepicker/-/datepicker-3.14.0.tgz" + integrity sha512-JSkQfKW0+WpPQyOOeRPBLwXkVfpTUwgZJDnHBCud5kEuQiFFyeAIbL57RNXc4AX2pzY3piQa6OHnjDGTfqClxQ== + dependencies: + "@internationalized/date" "^3.8.0" + "@internationalized/string" "^3.2.6" + "@react-stately/form" "^3.1.3" + "@react-stately/overlays" "^3.6.15" + "@react-stately/utils" "^3.10.6" + "@react-types/datepicker" "^3.12.0" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-stately/datepicker@^3.14.1": + version "3.14.1" + resolved "https://registry.npmjs.org/@react-stately/datepicker/-/datepicker-3.14.1.tgz" + integrity sha512-ad3IOrRppy/F8FZpznGacsaWWHdzUGZ4vpymD+y6TYeQ+RQvS9PLA5Z1TanH9iqLZgkf6bvVggJFg/hhDh2hmg== + dependencies: + "@internationalized/date" "^3.8.1" + "@internationalized/string" "^3.2.6" + "@react-stately/form" "^3.1.4" + "@react-stately/overlays" "^3.6.16" + "@react-stately/utils" "^3.10.6" + "@react-types/datepicker" "^3.12.1" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-stately/disclosure@^3.0.0", "@react-stately/disclosure@^3.0.3", "@react-stately/disclosure@^3.0.4": + version "3.0.4" + resolved "https://registry.npmjs.org/@react-stately/disclosure/-/disclosure-3.0.4.tgz" + integrity sha512-RE4hYnDYgsd5bi01z/hZHShRGKxW++xCA6PCufxtipc1sxZGUF4Sb1tTSIxOjh1dq5iDVdrAQAS6en0weaGgLA== + dependencies: + "@react-stately/utils" "^3.10.6" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-stately/dnd@^3.5.0", "@react-stately/dnd@^3.5.3", "@react-stately/dnd@^3.5.4": + version "3.5.4" + resolved "https://registry.npmjs.org/@react-stately/dnd/-/dnd-3.5.4.tgz" + integrity sha512-YkvkehpsSeGZPH7S7EYyLchSxZPhzShdf9Zjh6UAsM7mAcxjRsChMqsf6zuM+l0jgMo40Ka1mvwDYegz92Qkyg== + dependencies: + "@react-stately/selection" "^3.20.2" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-stately/flags@^3.1.1": + version "3.1.1" + resolved "https://registry.npmjs.org/@react-stately/flags/-/flags-3.1.1.tgz" + integrity sha512-XPR5gi5LfrPdhxZzdIlJDz/B5cBf63l4q6/AzNqVWFKgd0QqY5LvWJftXkklaIUpKSJkIKQb8dphuZXDtkWNqg== + dependencies: + "@swc/helpers" "^0.5.0" + +"@react-stately/form@^3.1.0", "@react-stately/form@^3.1.3", "@react-stately/form@^3.1.4": + version "3.1.4" + resolved "https://registry.npmjs.org/@react-stately/form/-/form-3.1.4.tgz" + integrity sha512-A6GOaZ9oEIo5/XOE+JT9Z8OBt0osIOfes4EcIxGS1C9ght/Smg0gNcIJ2/Wle8qmro4RoJcza2yJ+EglVOuE0w== + dependencies: + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-stately/grid@^3.11.1", "@react-stately/grid@^3.11.2": + version "3.11.2" + resolved "https://registry.npmjs.org/@react-stately/grid/-/grid-3.11.2.tgz" + integrity sha512-P0vfK5B1NW8glYD6QMrR2X/7UMXx2J8v48QIQV6KgLZjFbyXhzRb+MY0BoIy4tUfJL0yQU2GKbKKVSUIQxbv0g== + dependencies: + "@react-stately/collections" "^3.12.4" + "@react-stately/selection" "^3.20.2" + "@react-types/grid" "^3.3.2" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-stately/layout@^4.1.0": + version "4.2.2" + resolved "https://registry.npmjs.org/@react-stately/layout/-/layout-4.2.2.tgz" + integrity sha512-cKojNZteaVPtJrEePoNmKOgua4LYhholsthaEpD7ldKcOacl9VsvBbaowv945HEDKj6A919YoXOLdgy5qzoPtw== + dependencies: + "@react-stately/collections" "^3.12.3" + "@react-stately/table" "^3.14.1" + "@react-stately/virtualizer" "^4.3.2" + "@react-types/grid" "^3.3.1" + "@react-types/shared" "^3.29.0" + "@react-types/table" "^3.12.0" + "@swc/helpers" "^0.5.0" + +"@react-stately/layout@^4.2.2", "@react-stately/layout@^4.3.0": + version "4.3.0" + resolved "https://registry.npmjs.org/@react-stately/layout/-/layout-4.3.0.tgz" + integrity sha512-1czYPaWsEi/ecSOMBiMmH82iTeAIez/72HQjvP0i5CK2ZqLV0M1/Z10lesJHdOE+ay2EkE2qEqbHJnCdCqzkpA== + dependencies: + "@react-stately/collections" "^3.12.4" + "@react-stately/table" "^3.14.2" + "@react-stately/virtualizer" "^4.4.0" + "@react-types/grid" "^3.3.2" + "@react-types/shared" "^3.29.1" + "@react-types/table" "^3.13.0" + "@swc/helpers" "^0.5.0" + +"@react-stately/list@^3.11.1", "@react-stately/list@^3.12.1", "@react-stately/list@^3.12.2": + version "3.12.2" + resolved "https://registry.npmjs.org/@react-stately/list/-/list-3.12.2.tgz" + integrity sha512-XPGvdPidOV4hnpmaUNc4C/1jX7ZhBwmAI9p6bEXDA3du3XrWess6MWcaQvPxXbrZ6ZX8/OyOC2wp7ixJoJRGyA== + dependencies: + "@react-stately/collections" "^3.12.4" + "@react-stately/selection" "^3.20.2" + "@react-stately/utils" "^3.10.6" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-stately/menu@^3.9.0", "@react-stately/menu@^3.9.3", "@react-stately/menu@^3.9.4": + version "3.9.4" + resolved "https://registry.npmjs.org/@react-stately/menu/-/menu-3.9.4.tgz" + integrity sha512-sqYcSBuTEtCebZuByUou2aZzwlnrrOlrvmGwFNJy49N3LXXXPENCcCERuWa8TE9eBevIVTQorBZlID6rFG+wdQ== + dependencies: + "@react-stately/overlays" "^3.6.16" + "@react-types/menu" "^3.10.1" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-stately/numberfield@^3.9.11", "@react-stately/numberfield@^3.9.12", "@react-stately/numberfield@^3.9.8": + version "3.9.12" + resolved "https://registry.npmjs.org/@react-stately/numberfield/-/numberfield-3.9.12.tgz" + integrity sha512-E56RuRRdu/lzd8e5aEifP4n8CL/as0sZqIQFSyMv/ZUIIGeksqy+zykzo01skaHKY8u2NixrVHPVDtvPcRuooA== + dependencies: + "@internationalized/number" "^3.6.2" + "@react-stately/form" "^3.1.4" + "@react-stately/utils" "^3.10.6" + "@react-types/numberfield" "^3.8.11" + "@swc/helpers" "^0.5.0" + +"@react-stately/overlays@^3.6.12", "@react-stately/overlays@^3.6.15", "@react-stately/overlays@^3.6.16": + version "3.6.16" + resolved "https://registry.npmjs.org/@react-stately/overlays/-/overlays-3.6.16.tgz" + integrity sha512-+Ve/TBlUNg3otVC4ZfCq1a8q8FwC7xNebWkVOCGviTqiYodPCGqBwR9Z1xonuFLF/HuQYqALHHTOZtxceU+nVQ== + dependencies: + "@react-stately/utils" "^3.10.6" + "@react-types/overlays" "^3.8.15" + "@swc/helpers" "^0.5.0" + +"@react-stately/radio@^3.10.12", "@react-stately/radio@^3.10.13", "@react-stately/radio@^3.10.9": + version "3.10.13" + resolved "https://registry.npmjs.org/@react-stately/radio/-/radio-3.10.13.tgz" + integrity sha512-q7UKcVYY7rqpxKfYRzvKVEqFhxElDFX2c+xliZQtjXuSexhxRb2xjEh+bDkhzbXzrJkrBT6VmE/rSYPurC3xTw== + dependencies: + "@react-stately/form" "^3.1.4" + "@react-stately/utils" "^3.10.6" + "@react-types/radio" "^3.8.9" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-stately/searchfield@^3.5.11", "@react-stately/searchfield@^3.5.12", "@react-stately/searchfield@^3.5.8": + version "3.5.12" + resolved "https://registry.npmjs.org/@react-stately/searchfield/-/searchfield-3.5.12.tgz" + integrity sha512-RC3QTEPVNUbgtuqzpwPUfbV9UkUC1j4XkHoynWDbMt0bE0tPe2Picnl0/r/kq6MO527idV6Ur4zuOF4x9a97LQ== + dependencies: + "@react-stately/utils" "^3.10.6" + "@react-types/searchfield" "^3.6.2" + "@swc/helpers" "^0.5.0" + +"@react-stately/select@^3.6.12", "@react-stately/select@^3.6.13", "@react-stately/select@^3.6.9": + version "3.6.13" + resolved "https://registry.npmjs.org/@react-stately/select/-/select-3.6.13.tgz" + integrity sha512-saZo67CreQZPdmqvz9+P6N4kjohpwdVncH98qBi0Q2FvxGAMnpJQgx97rtfDvnSziST5Yx1JnMI4kSSndbtFwg== + dependencies: + "@react-stately/form" "^3.1.4" + "@react-stately/list" "^3.12.2" + "@react-stately/overlays" "^3.6.16" + "@react-types/select" "^3.9.12" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-stately/selection@^3.18.0", "@react-stately/selection@^3.20.1", "@react-stately/selection@^3.20.2": + version "3.20.2" + resolved "https://registry.npmjs.org/@react-stately/selection/-/selection-3.20.2.tgz" + integrity sha512-Fw6nnG+VKMsncsY4SNxGYOhnHojVFzFv+Uhy6P39QBp6AXtSaRKMg2VR4MPxQ7XgOjHh5ZuSvCY1RwocweqjwQ== + dependencies: + "@react-stately/collections" "^3.12.4" + "@react-stately/utils" "^3.10.6" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-stately/slider@^3.6.0", "@react-stately/slider@^3.6.3", "@react-stately/slider@^3.6.4": + version "3.6.4" + resolved "https://registry.npmjs.org/@react-stately/slider/-/slider-3.6.4.tgz" + integrity sha512-6SdG0VJZLMRIBnPjqkbIsdyQcW9zJ5Br716cl/7kLT9owiIwMJiAdjdYHab5+8ShWzU2D8Ae+LdQk8ZxIiIjkg== + dependencies: + "@react-stately/utils" "^3.10.6" + "@react-types/shared" "^3.29.1" + "@react-types/slider" "^3.7.11" + "@swc/helpers" "^0.5.0" + +"@react-stately/table@^3.13.0", "@react-stately/table@^3.14.1", "@react-stately/table@^3.14.2": + version "3.14.2" + resolved "https://registry.npmjs.org/@react-stately/table/-/table-3.14.2.tgz" + integrity sha512-SqE5A/Ve5H2ApnAblMGBMGRzY7cgdQmNPzXB8tGVc38NsC/STmMkq9m54gAl8dBVNbLzzd6HJBe9lqz5keYIhQ== + dependencies: + "@react-stately/collections" "^3.12.4" + "@react-stately/flags" "^3.1.1" + "@react-stately/grid" "^3.11.2" + "@react-stately/selection" "^3.20.2" + "@react-stately/utils" "^3.10.6" + "@react-types/grid" "^3.3.2" + "@react-types/shared" "^3.29.1" + "@react-types/table" "^3.13.0" + "@swc/helpers" "^0.5.0" + +"@react-stately/tabs@^3.7.0", "@react-stately/tabs@^3.8.1", "@react-stately/tabs@^3.8.2": + version "3.8.2" + resolved "https://registry.npmjs.org/@react-stately/tabs/-/tabs-3.8.2.tgz" + integrity sha512-lNpby7zUVdAeqo3mjGdPBxppEskOLyqR82LWBtP8Xg4olnjA5RmDFOuoJkIFttDX689zamjN3OE+Ra6WWgJczg== + dependencies: + "@react-stately/list" "^3.12.2" + "@react-types/shared" "^3.29.1" + "@react-types/tabs" "^3.3.15" + "@swc/helpers" "^0.5.0" + +"@react-stately/toast@^3.1.0": + version "3.1.0" + resolved "https://registry.npmjs.org/@react-stately/toast/-/toast-3.1.0.tgz" + integrity sha512-9W2+evz+EARrjkR1QPLlOL5lcNpVo6PjMAIygRSaCPJ6ftQAZ6B+7xTFGPFabWh83gwXQDUgoSwC3/vosvxZaQ== + dependencies: + "@swc/helpers" "^0.5.0" + use-sync-external-store "^1.4.0" + +"@react-stately/toggle@^3.8.0", "@react-stately/toggle@^3.8.3", "@react-stately/toggle@^3.8.4": + version "3.8.4" + resolved "https://registry.npmjs.org/@react-stately/toggle/-/toggle-3.8.4.tgz" + integrity sha512-JbKoXhkJ5P5nCrNXChMos3yNqkIeGXPDEMS/dfkHlsjQYxJfylRm4j/nWoDXxxkUmfkvXcNEMofMn9iO1+H0DQ== + dependencies: + "@react-stately/utils" "^3.10.6" + "@react-types/checkbox" "^3.9.4" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-stately/tooltip@^3.5.0", "@react-stately/tooltip@^3.5.3", "@react-stately/tooltip@^3.5.4": + version "3.5.4" + resolved "https://registry.npmjs.org/@react-stately/tooltip/-/tooltip-3.5.4.tgz" + integrity sha512-HxNTqn9nMBuGbEVeeuZyhrzNbyW7sgwk+8o0mN/BrMrk7E/UBhyL2SUxXnAUQftpTjX+29hmx1sPhIprIDzR3Q== + dependencies: + "@react-stately/overlays" "^3.6.16" + "@react-types/tooltip" "^3.4.17" + "@swc/helpers" "^0.5.0" + +"@react-stately/tree@^3.8.10", "@react-stately/tree@^3.8.6", "@react-stately/tree@^3.8.9": + version "3.8.10" + resolved "https://registry.npmjs.org/@react-stately/tree/-/tree-3.8.10.tgz" + integrity sha512-sMqBRKAAZMiXJwlzAFpkXqUaGlNBfKnL8usAiKdoeGcLLJt2Ni9gPoPOLBJSPqLOAFCgLWtr5IYjdhel9aXRzQ== + dependencies: + "@react-stately/collections" "^3.12.4" + "@react-stately/selection" "^3.20.2" + "@react-stately/utils" "^3.10.6" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-stately/utils@^3.10.5", "@react-stately/utils@^3.10.6": + version "3.10.6" + resolved "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.6.tgz" + integrity sha512-O76ip4InfTTzAJrg8OaZxKU4vvjMDOpfA/PGNOytiXwBbkct2ZeZwaimJ8Bt9W1bj5VsZ81/o/tW4BacbdDOMA== + dependencies: + "@swc/helpers" "^0.5.0" + +"@react-stately/virtualizer@^4.2.0": + version "4.3.2" + resolved "https://registry.npmjs.org/@react-stately/virtualizer/-/virtualizer-4.3.2.tgz" + integrity sha512-KxR0s6IBqUD2TfDM3mAOtiTZLb1zOwcuCeUOvCKNqzEdFhh7nEJPrG33mgJn64S4kM11c0AsPwBlxISqdvCXJg== + dependencies: + "@react-aria/utils" "^3.28.2" + "@react-types/shared" "^3.29.0" + "@swc/helpers" "^0.5.0" + +"@react-stately/virtualizer@^4.3.2", "@react-stately/virtualizer@^4.4.0": + version "4.4.0" + resolved "https://registry.npmjs.org/@react-stately/virtualizer/-/virtualizer-4.4.0.tgz" + integrity sha512-y2jefrW0ffJpv0685IEKId6/wy0kgD/bxYuny9r9Z3utvcjjFl9fX9cBKsXII7ZxPiu0CP+wA6HQ53GU3BqCsw== + dependencies: + "@react-aria/utils" "^3.29.0" + "@react-types/shared" "^3.29.1" + "@swc/helpers" "^0.5.0" + +"@react-types/autocomplete@3.0.0-alpha.30": + version "3.0.0-alpha.30" + resolved "https://registry.npmjs.org/@react-types/autocomplete/-/autocomplete-3.0.0-alpha.30.tgz" + integrity sha512-9neGygI+stJqiEFHzoc1jMySj6lOc4MUmBmu0uGn2zdOG2zxaAZSjh1pd9AJkHNyZ4j/n5rVXMo+v3RNkUntNw== + dependencies: + "@react-types/combobox" "^3.13.4" + "@react-types/searchfield" "^3.6.1" + "@react-types/shared" "^3.29.0" + +"@react-types/autocomplete@3.0.0-alpha.31": + version "3.0.0-alpha.31" + resolved "https://registry.npmjs.org/@react-types/autocomplete/-/autocomplete-3.0.0-alpha.31.tgz" + integrity sha512-L+5JtCAM+Y2/hCQ0BYXti6P2KGyiEM7FTYFBaTr2CoaHDN3u8e3cpDjOig83zzs9FcdUClovkqpVtvu26IZvhw== + dependencies: + "@react-types/combobox" "^3.13.5" + "@react-types/searchfield" "^3.6.2" + "@react-types/shared" "^3.29.1" + +"@react-types/breadcrumbs@^3.7.12", "@react-types/breadcrumbs@^3.7.13": + version "3.7.13" + resolved "https://registry.npmjs.org/@react-types/breadcrumbs/-/breadcrumbs-3.7.13.tgz" + integrity sha512-x94KEZaLIeHt9lqAkuaOopX5+rqCTMSHsciThUsBHK7QT64zrw6x2G1WKQ4zB4h52RGF5b+3sFXeR4bgX2sVLQ== + dependencies: + "@react-types/link" "^3.6.1" + "@react-types/shared" "^3.29.1" + +"@react-types/button@^3.10.1", "@react-types/button@^3.12.0", "@react-types/button@^3.12.1": + version "3.12.1" + resolved "https://registry.npmjs.org/@react-types/button/-/button-3.12.1.tgz" + integrity sha512-z87stl4llWTi4C5qhUK1PKcEsG59uF/ZQpkRhMzX0KfgXobJY6yiIrry2xrpnlTPIVST6K1+kARhhSDOZ8zhLw== + dependencies: + "@react-types/shared" "^3.29.1" + +"@react-types/calendar@^3.5.0", "@react-types/calendar@^3.7.0", "@react-types/calendar@^3.7.1": + version "3.7.1" + resolved "https://registry.npmjs.org/@react-types/calendar/-/calendar-3.7.1.tgz" + integrity sha512-a/wGT9vZewPNL72Xni8T/gv4IS2w6iRtryqMF425OL+kaCQrxJYlkDxb74bQs9+k9ZYabrxJgz9vFcFnY7S9gw== + dependencies: + "@internationalized/date" "^3.8.1" + "@react-types/shared" "^3.29.1" + +"@react-types/checkbox@^3.9.3", "@react-types/checkbox@^3.9.4": + version "3.9.4" + resolved "https://registry.npmjs.org/@react-types/checkbox/-/checkbox-3.9.4.tgz" + integrity sha512-fU3Q1Nw+zbXKm68ba8V7cQzpiX0rIiAUKrBTl2BK97QiTlGBDvMCf4TfEuaNoGbJq+gx+X3n/3yr6c3IAb0ZIg== + dependencies: + "@react-types/shared" "^3.29.1" + +"@react-types/color@^3.0.1", "@react-types/color@^3.0.4", "@react-types/color@^3.0.5": + version "3.0.5" + resolved "https://registry.npmjs.org/@react-types/color/-/color-3.0.5.tgz" + integrity sha512-72uZ0B3EcaC2DGOpnhwHSVxcvQ3UDNSVR2gVx7PgUCGlEjhnn9i0UErIP8ZzV2RsAvjK6MrGs7ZCwZtl+LxCcg== + dependencies: + "@react-types/shared" "^3.29.1" + "@react-types/slider" "^3.7.11" + +"@react-types/combobox@^3.13.4", "@react-types/combobox@^3.13.5": + version "3.13.5" + resolved "https://registry.npmjs.org/@react-types/combobox/-/combobox-3.13.5.tgz" + integrity sha512-wqHBF0YDkrp4Ylyxpd3xhnDECe5eao27bsu+4AvjlVKtaxaoppNq2MwSzkuSSS/GEUXT6K9DDjrGFcp07ad5gA== + dependencies: + "@react-types/shared" "^3.29.1" + +"@react-types/datepicker@^3.12.0", "@react-types/datepicker@^3.12.1", "@react-types/datepicker@^3.9.0": + version "3.12.1" + resolved "https://registry.npmjs.org/@react-types/datepicker/-/datepicker-3.12.1.tgz" + integrity sha512-+wv57fVd6Y/+KnHNEmVzfrQtWs85Ga1Xb63AIkBk+E294aMqFYqRg0dQds6V/qrP758TWnXUrhKza1zMbjHalw== + dependencies: + "@internationalized/date" "^3.8.1" + "@react-types/calendar" "^3.7.1" + "@react-types/overlays" "^3.8.15" + "@react-types/shared" "^3.29.1" + +"@react-types/dialog@^3.5.14", "@react-types/dialog@^3.5.17", "@react-types/dialog@^3.5.18": + version "3.5.18" + resolved "https://registry.npmjs.org/@react-types/dialog/-/dialog-3.5.18.tgz" + integrity sha512-g18CzT5xmiX/numpS6MrOGEGln8Xp9rr+zO70Dg+jM4GBOjXZp3BeclYQr9uisxGaj2uFLnORv9gNMMKxLNF6A== + dependencies: + "@react-types/overlays" "^3.8.15" + "@react-types/shared" "^3.29.1" + +"@react-types/form@^3.7.11", "@react-types/form@^3.7.12", "@react-types/form@^3.7.8": + version "3.7.12" + resolved "https://registry.npmjs.org/@react-types/form/-/form-3.7.12.tgz" + integrity sha512-EZ6jZDa9FbLmqvukrLoUp3LUEVE0ZnBB5H6MHhE+QmjYRAvtWljx70xOqnn7sHweuS4+O1kDt1Ec1X5DU+U+BA== + dependencies: + "@react-types/shared" "^3.29.1" + +"@react-types/grid@^3.2.10", "@react-types/grid@^3.3.1", "@react-types/grid@^3.3.2": + version "3.3.2" + resolved "https://registry.npmjs.org/@react-types/grid/-/grid-3.3.2.tgz" + integrity sha512-NwfydUbPc1zVi/Rp7+oRN2+vE1xMokc2J+nr0VcHwFGt1bR1psakHu45Pk/t763BDvPr/A3xIHc1rk3eWEhxJw== + dependencies: + "@react-types/shared" "^3.29.1" + +"@react-types/link@^3.6.0", "@react-types/link@^3.6.1": + version "3.6.1" + resolved "https://registry.npmjs.org/@react-types/link/-/link-3.6.1.tgz" + integrity sha512-IZDSc10AuVKe7V8Te+3q8d220oANE4N43iljQe3yHg7GZOfH/51bv8FPUukreLs1t2fgtGeNAzG71Ep+j/jXIw== + dependencies: + "@react-types/shared" "^3.29.1" + +"@react-types/listbox@^3.6.0", "@react-types/listbox@^3.7.0": + version "3.7.0" + resolved "https://registry.npmjs.org/@react-types/listbox/-/listbox-3.7.0.tgz" + integrity sha512-26Lp0Gou502VJLDSrIpMg7LQuVHznxzyuSY/zzyNX9eopukXvHn682u90fwDqgmZz7dzxUOWtuwDea+bp/UjtA== + dependencies: + "@react-types/shared" "^3.29.1" + +"@react-types/menu@^3.10.0", "@react-types/menu@^3.10.1": + version "3.10.1" + resolved "https://registry.npmjs.org/@react-types/menu/-/menu-3.10.1.tgz" + integrity sha512-wkyWzIqaCbUYiD7YXr8YvdimB1bxQHqgj6uE4MKzryCbVqb4L8fRUM0V6AHkQS1TxBYNkNn1h4g7XNd5Vmyf3Q== + dependencies: + "@react-types/overlays" "^3.8.15" + "@react-types/shared" "^3.29.1" + +"@react-types/meter@^3.4.8", "@react-types/meter@^3.4.9": + version "3.4.9" + resolved "https://registry.npmjs.org/@react-types/meter/-/meter-3.4.9.tgz" + integrity sha512-Jhd873zc/Bx/86NB9nasMUWc013VnURVtMYbbkuRWiFr/ZoEvZzO1uoSIXf+Sob4xpiVhT/ltvJZTK4t4B9lTg== + dependencies: + "@react-types/progress" "^3.5.12" + +"@react-types/numberfield@^3.8.10", "@react-types/numberfield@^3.8.11": + version "3.8.11" + resolved "https://registry.npmjs.org/@react-types/numberfield/-/numberfield-3.8.11.tgz" + integrity sha512-D66Bop7M3JKzBV2vsECsVYfPrx8eRIx4/K2KLo/XjwMA7C34+Ou07f/bnD1TQQ/wr6XwiFxZTi6JsKDwnST+9Q== + dependencies: + "@react-types/shared" "^3.29.1" + +"@react-types/overlays@^3.8.14", "@react-types/overlays@^3.8.15": + version "3.8.15" + resolved "https://registry.npmjs.org/@react-types/overlays/-/overlays-3.8.15.tgz" + integrity sha512-ppDfezvVYOJDHLZmTSmIXajxAo30l2a1jjy4G65uBYy8J8kTZU7mcfQql5Pii1TwybcNMsayf2WtPItiWmJnOA== + dependencies: + "@react-types/shared" "^3.29.1" + +"@react-types/progress@^3.5.11", "@react-types/progress@^3.5.12": + version "3.5.12" + resolved "https://registry.npmjs.org/@react-types/progress/-/progress-3.5.12.tgz" + integrity sha512-wvhFz6vdlfKBtnzKvD/89N+0PF3yPQ+IVFRQvZ2TBrP7nF+ZA2pNLcZVcEYbKjHzmvEZRGu//ePC9hRJD9K30w== + dependencies: + "@react-types/shared" "^3.29.1" + +"@react-types/radio@^3.8.8", "@react-types/radio@^3.8.9": + version "3.8.9" + resolved "https://registry.npmjs.org/@react-types/radio/-/radio-3.8.9.tgz" + integrity sha512-l4uzlxmGGuR8IkWrMYdKj1sc3Pgo/LdfEGuIgK+d8kjPu0AZcnSgp5Oz035bCosZUabY6dEWxQHIoAH2zN7YZA== + dependencies: + "@react-types/shared" "^3.29.1" + +"@react-types/searchfield@^3.6.1", "@react-types/searchfield@^3.6.2": + version "3.6.2" + resolved "https://registry.npmjs.org/@react-types/searchfield/-/searchfield-3.6.2.tgz" + integrity sha512-XQRQyJLNC9uLyCq+97eiqeQuM6+dCMrHu6aH6KSVt1Xh6HMmdx/TdSf6JrMkN+1xSxcW3lDE2iSf3jXDT87gag== + dependencies: + "@react-types/shared" "^3.29.1" + "@react-types/textfield" "^3.12.2" + +"@react-types/select@^3.9.11", "@react-types/select@^3.9.12": + version "3.9.12" + resolved "https://registry.npmjs.org/@react-types/select/-/select-3.9.12.tgz" + integrity sha512-qo+9JS1kfMxuibmSmMp0faGKbeVftYnSk1f7Rh5PKi4tzMe3C0A9IAr27hUOfWeJMBOdetaoTpYmoXW6+CgW3g== + dependencies: + "@react-types/shared" "^3.29.1" + +"@react-types/shared@^3.26.0", "@react-types/shared@^3.29.0", "@react-types/shared@^3.29.1": + version "3.29.1" + resolved "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.1.tgz" + integrity sha512-KtM+cDf2CXoUX439rfEhbnEdAgFZX20UP2A35ypNIawR7/PFFPjQDWyA2EnClCcW/dLWJDEPX2U8+EJff8xqmQ== + +"@react-types/slider@^3.7.10", "@react-types/slider@^3.7.11": + version "3.7.11" + resolved "https://registry.npmjs.org/@react-types/slider/-/slider-3.7.11.tgz" + integrity sha512-uNhNLhVrt/2teXBOJSoZXyXg308A72qe1HOmlGdJcnh8iXA35y5ZHzeK1P6ZOJ37Aeh7bYGm3/UdURmFgSlW7w== + dependencies: + "@react-types/shared" "^3.29.1" + +"@react-types/switch@^3.5.10", "@react-types/switch@^3.5.11": + version "3.5.11" + resolved "https://registry.npmjs.org/@react-types/switch/-/switch-3.5.11.tgz" + integrity sha512-PJbZHwlE98OSuLzI6b1ei6Qa+FaiwlCRH3tOTdx/wPSdqmD3mRWEn7E9ftM6FC8hnxl/LrGLszQMT62yEQp5vQ== + dependencies: + "@react-types/shared" "^3.29.1" + +"@react-types/table@^3.10.3", "@react-types/table@^3.12.0", "@react-types/table@^3.13.0": + version "3.13.0" + resolved "https://registry.npmjs.org/@react-types/table/-/table-3.13.0.tgz" + integrity sha512-kn+OsEWJfUSSb4N4J0yl+tqx5grDpcaWcu2J8hA62hQCr/Leuj946ScYaKA9a/p0MAaOAaeCWx/Zcss6F8gJIQ== + dependencies: + "@react-types/grid" "^3.3.2" + "@react-types/shared" "^3.29.1" + +"@react-types/tabs@^3.3.14", "@react-types/tabs@^3.3.15": + version "3.3.15" + resolved "https://registry.npmjs.org/@react-types/tabs/-/tabs-3.3.15.tgz" + integrity sha512-VLgh9YLQdS4FQSk0sGTNHEVN2jeC0fZvOqEFHaEDgDyDgVOukxYuHjqVIx2IavYu1yNBrGO2b6P4M6dF+hcgwQ== + dependencies: + "@react-types/shared" "^3.29.1" + +"@react-types/textfield@^3.12.1", "@react-types/textfield@^3.12.2": + version "3.12.2" + resolved "https://registry.npmjs.org/@react-types/textfield/-/textfield-3.12.2.tgz" + integrity sha512-dMm0cGLG5bkJYvt6lqXIty5HXTZjuIpa9I8jAIYua//J8tESAOE9BA285Zl43kx7cZGtgrHKHVFjITDLNUrNhA== + dependencies: + "@react-types/shared" "^3.29.1" + +"@react-types/tooltip@^3.4.16", "@react-types/tooltip@^3.4.17": + version "3.4.17" + resolved "https://registry.npmjs.org/@react-types/tooltip/-/tooltip-3.4.17.tgz" + integrity sha512-yjySKA1uzJAbio+xGv03DUoWIajteqtsXMd4Y3AJEdBFqSYhXbyrgAxw0oJDgRAgRxY4Rx5Hrhvbt/z7Di94QQ== + dependencies: + "@react-types/overlays" "^3.8.15" + "@react-types/shared" "^3.29.1" + +"@rollup/plugin-babel@^5.2.0": + version "5.3.1" + resolved "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz" + integrity sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q== + dependencies: + "@babel/helper-module-imports" "^7.10.4" + "@rollup/pluginutils" "^3.1.0" + +"@rollup/plugin-commonjs@28.0.1": + version "28.0.1" + resolved "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.1.tgz" + integrity sha512-+tNWdlWKbpB3WgBN7ijjYkq9X5uhjmcvyjEght4NmH5fAU++zfQzAJ6wumLS+dNcvwEZhKx2Z+skY8m7v0wGSA== + dependencies: + "@rollup/pluginutils" "^5.0.1" + commondir "^1.0.1" + estree-walker "^2.0.2" + fdir "^6.2.0" + is-reference "1.2.1" + magic-string "^0.30.3" + picomatch "^4.0.2" + +"@rollup/plugin-node-resolve@^15.2.3": + version "15.3.1" + resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz" + integrity sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA== + dependencies: + "@rollup/pluginutils" "^5.0.1" + "@types/resolve" "1.20.2" + deepmerge "^4.2.2" + is-module "^1.0.0" + resolve "^1.22.1" + +"@rollup/plugin-replace@^2.4.1": + version "2.4.2" + resolved "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz" + integrity sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg== + dependencies: + "@rollup/pluginutils" "^3.1.0" + magic-string "^0.25.7" + +"@rollup/plugin-terser@^0.4.3": + version "0.4.4" + resolved "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz" + integrity sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A== + dependencies: + serialize-javascript "^6.0.1" + smob "^1.0.0" + terser "^5.17.4" + +"@rollup/pluginutils@^3.1.0": + version "3.1.0" + resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz" + integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== + dependencies: + "@types/estree" "0.0.39" + estree-walker "^1.0.1" + picomatch "^2.2.2" + +"@rollup/pluginutils@^5.0.1": + version "5.1.4" + resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz" + integrity sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ== + dependencies: + "@types/estree" "^1.0.0" + estree-walker "^2.0.2" + picomatch "^4.0.2" + +"@rollup/rollup-android-arm-eabi@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.35.0.tgz#e1d7700735f7e8de561ef7d1fa0362082a180c43" + integrity sha512-uYQ2WfPaqz5QtVgMxfN6NpLD+no0MYHDBywl7itPYd3K5TjjSghNKmX8ic9S8NU8w81NVhJv/XojcHptRly7qQ== + +"@rollup/rollup-android-arm64@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.35.0.tgz#fa6cdfb1fc9e2c8e227a7f35d524d8f7f90cf4db" + integrity sha512-FtKddj9XZudurLhdJnBl9fl6BwCJ3ky8riCXjEw3/UIbjmIY58ppWwPEvU3fNu+W7FUsAsB1CdH+7EQE6CXAPA== + +"@rollup/rollup-darwin-arm64@4.35.0": + version "4.35.0" + resolved "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.35.0.tgz" + integrity sha512-Uk+GjOJR6CY844/q6r5DR/6lkPFOw0hjfOIzVx22THJXMxktXG6CbejseJFznU8vHcEBLpiXKY3/6xc+cBm65Q== + +"@rollup/rollup-darwin-x64@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.35.0.tgz#25b74ce2d8d3f9ea8e119b01384d44a1c0a0d3ae" + integrity sha512-3IrHjfAS6Vkp+5bISNQnPogRAW5GAV1n+bNCrDwXmfMHbPl5EhTmWtfmwlJxFRUCBZ+tZ/OxDyU08aF6NI/N5Q== + +"@rollup/rollup-freebsd-arm64@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.35.0.tgz#be3d39e3441df5d6e187c83d158c60656c82e203" + integrity sha512-sxjoD/6F9cDLSELuLNnY0fOrM9WA0KrM0vWm57XhrIMf5FGiN8D0l7fn+bpUeBSU7dCgPV2oX4zHAsAXyHFGcQ== + +"@rollup/rollup-freebsd-x64@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.35.0.tgz#cd932d3ec679711efd65ca25821fb318e25b7ce4" + integrity sha512-2mpHCeRuD1u/2kruUiHSsnjWtHjqVbzhBkNVQ1aVD63CcexKVcQGwJ2g5VphOd84GvxfSvnnlEyBtQCE5hxVVw== + +"@rollup/rollup-linux-arm-gnueabihf@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.35.0.tgz#d300b74c6f805474225632f185daaeae760ac2bb" + integrity sha512-mrA0v3QMy6ZSvEuLs0dMxcO2LnaCONs1Z73GUDBHWbY8tFFocM6yl7YyMu7rz4zS81NDSqhrUuolyZXGi8TEqg== + +"@rollup/rollup-linux-arm-musleabihf@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.35.0.tgz#2caac622380f314c41934ed1e68ceaf6cc380cc3" + integrity sha512-DnYhhzcvTAKNexIql8pFajr0PiDGrIsBYPRvCKlA5ixSS3uwo/CWNZxB09jhIapEIg945KOzcYEAGGSmTSpk7A== + +"@rollup/rollup-linux-arm64-gnu@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.35.0.tgz#1ec841650b038cc15c194c26326483fd7ebff3e3" + integrity sha512-uagpnH2M2g2b5iLsCTZ35CL1FgyuzzJQ8L9VtlJ+FckBXroTwNOaD0z0/UF+k5K3aNQjbm8LIVpxykUOQt1m/A== + +"@rollup/rollup-linux-arm64-musl@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.35.0.tgz#2fc70a446d986e27f6101ea74e81746987f69150" + integrity sha512-XQxVOCd6VJeHQA/7YcqyV0/88N6ysSVzRjJ9I9UA/xXpEsjvAgDTgH3wQYz5bmr7SPtVK2TsP2fQ2N9L4ukoUg== + +"@rollup/rollup-linux-loongarch64-gnu@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.35.0.tgz#561bd045cd9ce9e08c95f42e7a8688af8c93d764" + integrity sha512-5pMT5PzfgwcXEwOaSrqVsz/LvjDZt+vQ8RT/70yhPU06PTuq8WaHhfT1LW+cdD7mW6i/J5/XIkX/1tCAkh1W6g== + +"@rollup/rollup-linux-powerpc64le-gnu@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.35.0.tgz#45d849a0b33813f33fe5eba9f99e0ff15ab5caad" + integrity sha512-c+zkcvbhbXF98f4CtEIP1EBA/lCic5xB0lToneZYvMeKu5Kamq3O8gqrxiYYLzlZH6E3Aq+TSW86E4ay8iD8EA== + +"@rollup/rollup-linux-riscv64-gnu@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.35.0.tgz#78dde3e6fcf5b5733a97d0a67482d768aa1e83a5" + integrity sha512-s91fuAHdOwH/Tad2tzTtPX7UZyytHIRR6V4+2IGlV0Cej5rkG0R61SX4l4y9sh0JBibMiploZx3oHKPnQBKe4g== + +"@rollup/rollup-linux-s390x-gnu@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.35.0.tgz#2e34835020f9e03dfb411473a5c2a0e8a9c5037b" + integrity sha512-hQRkPQPLYJZYGP+Hj4fR9dDBMIM7zrzJDWFEMPdTnTy95Ljnv0/4w/ixFw3pTBMEuuEuoqtBINYND4M7ujcuQw== + +"@rollup/rollup-linux-x64-gnu@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.35.0.tgz#4f9774beddc6f4274df57ac99862eb23040de461" + integrity sha512-Pim1T8rXOri+0HmV4CdKSGrqcBWX0d1HoPnQ0uw0bdp1aP5SdQVNBy8LjYncvnLgu3fnnCt17xjWGd4cqh8/hA== + +"@rollup/rollup-linux-x64-musl@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.35.0.tgz#dfcff2c1aed518b3d23ccffb49afb349d74fb608" + integrity sha512-QysqXzYiDvQWfUiTm8XmJNO2zm9yC9P/2Gkrwg2dH9cxotQzunBHYr6jk4SujCTqnfGxduOmQcI7c2ryuW8XVg== + +"@rollup/rollup-win32-arm64-msvc@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.35.0.tgz#b0b37e2d77041e3aa772f519291309abf4c03a84" + integrity sha512-OUOlGqPkVJCdJETKOCEf1mw848ZyJ5w50/rZ/3IBQVdLfR5jk/6Sr5m3iO2tdPgwo0x7VcncYuOvMhBWZq8ayg== + +"@rollup/rollup-win32-ia32-msvc@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.35.0.tgz#5b5a40e44a743ddc0e06b8e1b3982f856dc9ce0a" + integrity sha512-2/lsgejMrtwQe44glq7AFFHLfJBPafpsTa6JvP2NGef/ifOa4KBoglVf7AKN7EV9o32evBPRqfg96fEHzWo5kw== + +"@rollup/rollup-win32-x64-msvc@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.35.0.tgz#05f25dbc9981bee1ae6e713daab10397044a46ca" + integrity sha512-PIQeY5XDkrOysbQblSW7v3l1MDZzkTEzAfTPkj5VAu3FW8fS4ynyLg2sINp0fp3SjZ8xkRYpLqoKcYqAkhU1dw== + +"@rtsao/scc@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz" + integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== + +"@rushstack/eslint-patch@^1.10.3": + version "1.11.0" + resolved "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.11.0.tgz" + integrity sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ== + +"@sentry-internal/browser-utils@9.26.0": + version "9.26.0" + resolved "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.26.0.tgz" + integrity sha512-Ya4YQSzrM6TSuCuO+tUPK+WXFHfndaX73wszCmIu7UZlUHbKTZ5HVWxXxHW9f6KhVIHYzdYQMeA/4F4N7n+rgg== + dependencies: + "@sentry/core" "9.26.0" + +"@sentry-internal/feedback@9.26.0": + version "9.26.0" + resolved "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.26.0.tgz" + integrity sha512-XnN6UiFNGkJMCw8Oy9qnP2GW/ueiQOUEl8vaA28v0uAIL2cIMxJY7mrii9D3NNip8d/iPzpgDZJk2epBClfpyw== + dependencies: + "@sentry/core" "9.26.0" + +"@sentry-internal/replay-canvas@9.26.0": + version "9.26.0" + resolved "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.26.0.tgz" + integrity sha512-ABj5TRRI3WWgLFPHrncCLOL5On/K+TpsbwWCM58AXQwwvtsSN2R22RY0ftuYgmAzBt4tygUJ9VQfIAWcRtC5sQ== + dependencies: + "@sentry-internal/replay" "9.26.0" + "@sentry/core" "9.26.0" + +"@sentry-internal/replay@9.26.0": + version "9.26.0" + resolved "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.26.0.tgz" + integrity sha512-SrND17u9Of0Jal4i9fJLoi98puBU3CQxwWq1Vda5JI9nLNwVU00QRbcsXsiartp/e0A8m0yGsySlrAGb1tZTaA== + dependencies: + "@sentry-internal/browser-utils" "9.26.0" + "@sentry/core" "9.26.0" + +"@sentry/babel-plugin-component-annotate@3.5.0": + version "3.5.0" + resolved "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-3.5.0.tgz" + integrity sha512-s2go8w03CDHbF9luFGtBHKJp4cSpsQzNVqgIa9Pfa4wnjipvrK6CxVT4icpLA3YO6kg5u622Yoa5GF3cJdippw== + +"@sentry/browser@9.26.0": + version "9.26.0" + resolved "https://registry.npmjs.org/@sentry/browser/-/browser-9.26.0.tgz" + integrity sha512-aZFAXcNtJe+QQidIiB8wW8uyzBnIJR81CoeZkDxl1fJ0YlAZraazyD35DWP7suLKujCPtWNv3vRzSxYMxxP/NQ== + dependencies: + "@sentry-internal/browser-utils" "9.26.0" + "@sentry-internal/feedback" "9.26.0" + "@sentry-internal/replay" "9.26.0" + "@sentry-internal/replay-canvas" "9.26.0" + "@sentry/core" "9.26.0" + +"@sentry/bundler-plugin-core@3.5.0": + version "3.5.0" + resolved "https://registry.npmjs.org/@sentry/bundler-plugin-core/-/bundler-plugin-core-3.5.0.tgz" + integrity sha512-zDzPrhJqAAy2VzV4g540qAZH4qxzisstK2+NIJPZUUKztWRWUV2cMHsyUtdctYgloGkLyGpZJBE3RE6dmP/xqQ== + dependencies: + "@babel/core" "^7.18.5" + "@sentry/babel-plugin-component-annotate" "3.5.0" + "@sentry/cli" "2.42.2" + dotenv "^16.3.1" + find-up "^5.0.0" + glob "^9.3.2" + magic-string "0.30.8" + unplugin "1.0.1" + +"@sentry/cli-darwin@2.42.2": + version "2.42.2" + resolved "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.42.2.tgz" + integrity sha512-GtJSuxER7Vrp1IpxdUyRZzcckzMnb4N5KTW7sbTwUiwqARRo+wxS+gczYrS8tdgtmXs5XYhzhs+t4d52ITHMIg== + +"@sentry/cli-linux-arm64@2.42.2": + version "2.42.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.42.2.tgz#1c06c83ff21f51ec23acf5be3b1f8c7553bf86b1" + integrity sha512-BOxzI7sgEU5Dhq3o4SblFXdE9zScpz6EXc5Zwr1UDZvzgXZGosUtKVc7d1LmkrHP8Q2o18HcDWtF3WvJRb5Zpw== + +"@sentry/cli-linux-arm@2.42.2": + version "2.42.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.42.2.tgz#00cadc359ae3c051efb3e63873c033c61dbd1ca1" + integrity sha512-7udCw+YL9lwq+9eL3WLspvnuG+k5Icg92YE7zsteTzWLwgPVzaxeZD2f8hwhsu+wmL+jNqbpCRmktPteh3i2mg== + +"@sentry/cli-linux-i686@2.42.2": + version "2.42.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.42.2.tgz#3b817b715dd806c20dfbffd539725ad8089c310a" + integrity sha512-Sw/dQp5ZPvKnq3/y7wIJyxTUJYPGoTX/YeMbDs8BzDlu9to2LWV3K3r7hE7W1Lpbaw4tSquUHiQjP5QHCOS7aQ== + +"@sentry/cli-linux-x64@2.42.2": + version "2.42.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.42.2.tgz#ddf906bc3071cc79ce6e633eddcb76bb9068e688" + integrity sha512-mU4zUspAal6TIwlNLBV5oq6yYqiENnCWSxtSQVzWs0Jyq97wtqGNG9U+QrnwjJZ+ta/hvye9fvL2X25D/RxHQw== + +"@sentry/cli-win32-i686@2.42.2": + version "2.42.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.42.2.tgz#9036085c7c6ce455ad45fda411c55ff39c06eb95" + integrity sha512-iHvFHPGqgJMNqXJoQpqttfsv2GI3cGodeTq4aoVLU/BT3+hXzbV0x1VpvvEhncJkDgDicJpFLM8sEPHb3b8abw== + +"@sentry/cli-win32-x64@2.42.2": + version "2.42.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.42.2.tgz#7d6464b63f32c9f97fff428f246b1f039b402233" + integrity sha512-vPPGHjYoaGmfrU7xhfFxG7qlTBacroz5NdT+0FmDn6692D8IvpNXl1K+eV3Kag44ipJBBeR8g1HRJyx/F/9ACw== + +"@sentry/cli@2.42.2": + version "2.42.2" + resolved "https://registry.npmjs.org/@sentry/cli/-/cli-2.42.2.tgz" + integrity sha512-spb7S/RUumCGyiSTg8DlrCX4bivCNmU/A1hcfkwuciTFGu8l5CDc2I6jJWWZw8/0enDGxuj5XujgXvU5tr4bxg== + dependencies: + https-proxy-agent "^5.0.0" + node-fetch "^2.6.7" + progress "^2.0.3" + proxy-from-env "^1.1.0" + which "^2.0.2" + optionalDependencies: + "@sentry/cli-darwin" "2.42.2" + "@sentry/cli-linux-arm" "2.42.2" + "@sentry/cli-linux-arm64" "2.42.2" + "@sentry/cli-linux-i686" "2.42.2" + "@sentry/cli-linux-x64" "2.42.2" + "@sentry/cli-win32-i686" "2.42.2" + "@sentry/cli-win32-x64" "2.42.2" + +"@sentry/core@9.26.0": + version "9.26.0" + resolved "https://registry.npmjs.org/@sentry/core/-/core-9.26.0.tgz" + integrity sha512-XTFSqOPn6wsZgF3NLRVY/FjYCkFahZoR46BtLVmBliD60QZLChpya81slD3M8BgLQpjsA2q6N1xrQor1Rc29gg== + +"@sentry/nextjs@9.26.0": + version "9.26.0" + resolved "https://registry.npmjs.org/@sentry/nextjs/-/nextjs-9.26.0.tgz" + integrity sha512-baOIDZT98rgaYUF1ZY3nrlQhZF3TwvksxxekJnZ1uuZp6FfDEvfK+4ruJDlWlcblv/aNPJLRhI22xAyUsS1cMg== + dependencies: + "@opentelemetry/api" "^1.9.0" + "@opentelemetry/semantic-conventions" "^1.34.0" + "@rollup/plugin-commonjs" "28.0.1" + "@sentry-internal/browser-utils" "9.26.0" + "@sentry/core" "9.26.0" + "@sentry/node" "9.26.0" + "@sentry/opentelemetry" "9.26.0" + "@sentry/react" "9.26.0" + "@sentry/vercel-edge" "9.26.0" + "@sentry/webpack-plugin" "3.5.0" + chalk "3.0.0" + resolve "1.22.8" + rollup "4.35.0" + stacktrace-parser "^0.1.10" + +"@sentry/node@9.26.0": + version "9.26.0" + resolved "https://registry.npmjs.org/@sentry/node/-/node-9.26.0.tgz" + integrity sha512-B7VdUtXlg1Y8DeZMWc9gOIoSmGT9hkKepits+kmkZgjYlyPhZtT8a0fwUNBLYFYq1Ti/JzKWw3ZNIlg00BY40w== + dependencies: + "@opentelemetry/api" "^1.9.0" + "@opentelemetry/context-async-hooks" "^1.30.1" + "@opentelemetry/core" "^1.30.1" + "@opentelemetry/instrumentation" "^0.57.2" + "@opentelemetry/instrumentation-amqplib" "^0.46.1" + "@opentelemetry/instrumentation-connect" "0.43.1" + "@opentelemetry/instrumentation-dataloader" "0.16.1" + "@opentelemetry/instrumentation-express" "0.47.1" + "@opentelemetry/instrumentation-fs" "0.19.1" + "@opentelemetry/instrumentation-generic-pool" "0.43.1" + "@opentelemetry/instrumentation-graphql" "0.47.1" + "@opentelemetry/instrumentation-hapi" "0.45.2" + "@opentelemetry/instrumentation-http" "0.57.2" + "@opentelemetry/instrumentation-ioredis" "0.47.1" + "@opentelemetry/instrumentation-kafkajs" "0.7.1" + "@opentelemetry/instrumentation-knex" "0.44.1" + "@opentelemetry/instrumentation-koa" "0.47.1" + "@opentelemetry/instrumentation-lru-memoizer" "0.44.1" + "@opentelemetry/instrumentation-mongodb" "0.52.0" + "@opentelemetry/instrumentation-mongoose" "0.46.1" + "@opentelemetry/instrumentation-mysql" "0.45.1" + "@opentelemetry/instrumentation-mysql2" "0.45.2" + "@opentelemetry/instrumentation-pg" "0.51.1" + "@opentelemetry/instrumentation-redis-4" "0.46.1" + "@opentelemetry/instrumentation-tedious" "0.18.1" + "@opentelemetry/instrumentation-undici" "0.10.1" + "@opentelemetry/resources" "^1.30.1" + "@opentelemetry/sdk-trace-base" "^1.30.1" + "@opentelemetry/semantic-conventions" "^1.34.0" + "@prisma/instrumentation" "6.8.2" + "@sentry/core" "9.26.0" + "@sentry/opentelemetry" "9.26.0" + import-in-the-middle "^1.13.1" + minimatch "^9.0.0" + +"@sentry/opentelemetry@9.26.0": + version "9.26.0" + resolved "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-9.26.0.tgz" + integrity sha512-yVxRv6GtrtKFfNKpfb+b/focF4cKslInIN+HPzllQBoVebrq+KeCjUYzDEj9b6OwZGbUZDbQdxGRgXrrxcZUMg== + dependencies: + "@sentry/core" "9.26.0" + +"@sentry/react@9.26.0": + version "9.26.0" + resolved "https://registry.npmjs.org/@sentry/react/-/react-9.26.0.tgz" + integrity sha512-I2AreDlNK6bak5eRRLCRnphJTx8mrhXEZ0MiUMAbg0fcQ5kM/yf4C6LNJpMPa86UhBQDkXQus+Cb1uYQYNF7og== + dependencies: + "@sentry/browser" "9.26.0" + "@sentry/core" "9.26.0" + hoist-non-react-statics "^3.3.2" + +"@sentry/vercel-edge@9.26.0": + version "9.26.0" + resolved "https://registry.npmjs.org/@sentry/vercel-edge/-/vercel-edge-9.26.0.tgz" + integrity sha512-CO18exGB8fClwXxFqz9TpWYSQkq+fIqF1DZx3gLEsbt8x3giw5Fk4/itoBaQUBctXLa6+X9kQm5lSvf+wRkonQ== + dependencies: + "@opentelemetry/api" "^1.9.0" + "@sentry/core" "9.26.0" + +"@sentry/webpack-plugin@3.5.0": + version "3.5.0" + resolved "https://registry.npmjs.org/@sentry/webpack-plugin/-/webpack-plugin-3.5.0.tgz" + integrity sha512-xvclj0QY2HyU7uJLzOlHSrZQBDwfnGKJxp8mmlU4L7CwmK+8xMCqlO7tYZoqE4K/wU3c2xpXql70x8qmvNMxzQ== + dependencies: + "@sentry/bundler-plugin-core" "3.5.0" + unplugin "1.0.1" + uuid "^9.0.0" + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@sinonjs/commons@^3.0.0": + version "3.0.1" + resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@surma/rollup-plugin-off-main-thread@^2.2.3": + version "2.2.3" + resolved "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz" + integrity sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ== + dependencies: + ejs "^3.1.6" + json5 "^2.2.0" + magic-string "^0.25.0" + string.prototype.matchall "^4.0.6" + +"@svgr/babel-plugin-add-jsx-attribute@8.0.0": + version "8.0.0" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz" + integrity sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g== + +"@svgr/babel-plugin-remove-jsx-attribute@8.0.0": + version "8.0.0" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz" + integrity sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA== + +"@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0": + version "8.0.0" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz" + integrity sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA== + +"@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0": + version "8.0.0" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz" + integrity sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ== + +"@svgr/babel-plugin-svg-dynamic-title@8.0.0": + version "8.0.0" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz" + integrity sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og== + +"@svgr/babel-plugin-svg-em-dimensions@8.0.0": + version "8.0.0" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz" + integrity sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g== + +"@svgr/babel-plugin-transform-react-native-svg@8.1.0": + version "8.1.0" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz" + integrity sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q== + +"@svgr/babel-plugin-transform-svg-component@8.0.0": + version "8.0.0" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz" + integrity sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw== + +"@svgr/babel-preset@8.1.0": + version "8.1.0" + resolved "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz" + integrity sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug== + dependencies: + "@svgr/babel-plugin-add-jsx-attribute" "8.0.0" + "@svgr/babel-plugin-remove-jsx-attribute" "8.0.0" + "@svgr/babel-plugin-remove-jsx-empty-expression" "8.0.0" + "@svgr/babel-plugin-replace-jsx-attribute-value" "8.0.0" + "@svgr/babel-plugin-svg-dynamic-title" "8.0.0" + "@svgr/babel-plugin-svg-em-dimensions" "8.0.0" + "@svgr/babel-plugin-transform-react-native-svg" "8.1.0" + "@svgr/babel-plugin-transform-svg-component" "8.0.0" + +"@svgr/core@8.1.0": + version "8.1.0" + resolved "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz" + integrity sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA== + dependencies: + "@babel/core" "^7.21.3" + "@svgr/babel-preset" "8.1.0" + camelcase "^6.2.0" + cosmiconfig "^8.1.3" + snake-case "^3.0.4" + +"@svgr/hast-util-to-babel-ast@8.0.0": + version "8.0.0" + resolved "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz" + integrity sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q== + dependencies: + "@babel/types" "^7.21.3" + entities "^4.4.0" + +"@svgr/plugin-jsx@8.1.0": + version "8.1.0" + resolved "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz" + integrity sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA== + dependencies: + "@babel/core" "^7.21.3" + "@svgr/babel-preset" "8.1.0" + "@svgr/hast-util-to-babel-ast" "8.0.0" + svg-parser "^2.0.4" + +"@svgr/plugin-svgo@8.1.0": + version "8.1.0" + resolved "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz" + integrity sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA== + dependencies: + cosmiconfig "^8.1.3" + deepmerge "^4.3.1" + svgo "^3.0.2" + +"@svgr/webpack@8.1.0": + version "8.1.0" + resolved "https://registry.npmjs.org/@svgr/webpack/-/webpack-8.1.0.tgz" + integrity sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA== + dependencies: + "@babel/core" "^7.21.3" + "@babel/plugin-transform-react-constant-elements" "^7.21.3" + "@babel/preset-env" "^7.20.2" + "@babel/preset-react" "^7.18.6" + "@babel/preset-typescript" "^7.21.0" + "@svgr/core" "8.1.0" + "@svgr/plugin-jsx" "8.1.0" + "@svgr/plugin-svgo" "8.1.0" + +"@swc/counter@0.1.3": + version "0.1.3" + resolved "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz" + integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== + +"@swc/helpers@0.5.15": + version "0.5.15" + resolved "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz" + integrity sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g== + dependencies: + tslib "^2.8.0" + +"@swc/helpers@^0.5.0", "@swc/helpers@^0.5.12": + version "0.5.17" + resolved "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz" + integrity sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A== + dependencies: + tslib "^2.8.0" + +"@tanstack/eslint-plugin-query@5.78.0": + version "5.78.0" + resolved "https://registry.npmjs.org/@tanstack/eslint-plugin-query/-/eslint-plugin-query-5.78.0.tgz" + integrity sha512-hYkhWr3UP0CkAsn/phBVR98UQawbw8CmTSgWtdgEBUjI60/GBaEIkpgi/Bp/2I8eIDK4+vdY7ac6jZx+GR+hEQ== + dependencies: + "@typescript-eslint/utils" "^8.18.1" + +"@tanstack/query-core@5.80.5": + version "5.80.5" + resolved "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.80.5.tgz" + integrity sha512-kFWXdQOUcjL/Ugk3GrI9eMuG3DsKBGaLIgyOLekR2UOrRrJgkLgPUNzZ10i8FCkfi4SgLABhOtQhx1HjoB9EZQ== + +"@tanstack/query-devtools@5.80.0": + version "5.80.0" + resolved "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.80.0.tgz" + integrity sha512-D6gH4asyjaoXrCOt5vG5Og/YSj0D/TxwNQgtLJIgWbhbWCC/emu2E92EFoVHh4ppVWg1qT2gKHvKyQBEFZhCuA== + +"@tanstack/react-query-devtools@5.80.5": + version "5.80.5" + resolved "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.80.5.tgz" + integrity sha512-+amzmlESL7e02Le28mEE8W5oiK5j/N/lBWu/dO3IbrD24SlP8cnfvYMMWTxLVkqZ9DJWEB3BqML8snAovl03uw== + dependencies: + "@tanstack/query-devtools" "5.80.0" + +"@tanstack/react-query@5.80.5": + version "5.80.5" + resolved "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.80.5.tgz" + integrity sha512-C0d+pvIahk6fJK5bXxyf36r9Ft6R9O0mwl781CjBrYGRJc76XRJcKhkVpxIo68cjMy3i47gd4O1EGooAke/OCQ== + dependencies: + "@tanstack/query-core" "5.80.5" + +"@tanstack/react-table@8.20.6": + version "8.20.6" + resolved "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.20.6.tgz" + integrity sha512-w0jluT718MrOKthRcr2xsjqzx+oEM7B7s/XXyfs19ll++hlId3fjTm+B2zrR3ijpANpkzBAr15j1XGVOMxpggQ== + dependencies: + "@tanstack/table-core" "8.20.5" + +"@tanstack/react-table@8.21.3": + version "8.21.3" + resolved "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz" + integrity sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww== + dependencies: + "@tanstack/table-core" "8.21.3" + +"@tanstack/table-core@8.20.5": + version "8.20.5" + resolved "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.20.5.tgz" + integrity sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg== + +"@tanstack/table-core@8.21.3": + version "8.21.3" + resolved "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz" + integrity sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg== + +"@testing-library/dom@10.4.0": + version "10.4.0" + resolved "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz" + integrity sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.12.5" + "@types/aria-query" "^5.0.1" + aria-query "5.3.0" + chalk "^4.1.0" + dom-accessibility-api "^0.5.9" + lz-string "^1.5.0" + pretty-format "^27.0.2" + +"@testing-library/jest-dom@6.6.3": + version "6.6.3" + resolved "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz" + integrity sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA== + dependencies: + "@adobe/css-tools" "^4.4.0" + aria-query "^5.0.0" + chalk "^3.0.0" + css.escape "^1.5.1" + dom-accessibility-api "^0.6.3" + lodash "^4.17.21" + redent "^3.0.0" + +"@testing-library/react@16.3.0": + version "16.3.0" + resolved "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz" + integrity sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw== + dependencies: + "@babel/runtime" "^7.12.5" + +"@testing-library/user-event@14.6.1": + version "14.6.1" + resolved "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz" + integrity sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw== + +"@tootallnate/once@2": + version "2.0.0" + resolved "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz" + integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== + +"@trysound/sax@0.2.0": + version "0.2.0" + resolved "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz" + integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== + +"@tsconfig/node10@^1.0.7": + version "1.0.11" + resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz" + integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + +"@tybys/wasm-util@^0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.9.0.tgz#3e75eb00604c8d6db470bf18c37b7d984a0e3355" + integrity sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw== + dependencies: + tslib "^2.4.0" + +"@types/aria-query@^5.0.1": + version "5.0.4" + resolved "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz" + integrity sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw== + +"@types/babel__core@^7.1.14": + version "7.20.5" + resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.27.0" + resolved "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz" + integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.20.7" + resolved "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz" + integrity sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng== + dependencies: + "@babel/types" "^7.20.7" + +"@types/connect@3.4.38": + version "3.4.38" + resolved "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== + dependencies: + "@types/node" "*" + +"@types/debug@^4.0.0": + version "4.1.12" + resolved "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz" + integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== + dependencies: + "@types/ms" "*" + +"@types/eslint-scope@^3.7.7": + version "3.7.7" + resolved "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz" + integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*": + version "9.6.1" + resolved "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz" + integrity sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree-jsx@^1.0.0": + version "1.0.5" + resolved "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz" + integrity sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg== + dependencies: + "@types/estree" "*" + +"@types/estree@*", "@types/estree@^1.0.0", "@types/estree@^1.0.6": + version "1.0.7" + resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz" + integrity sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ== + +"@types/estree@0.0.39": + version "0.0.39" + resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz" + integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== + +"@types/estree@1.0.6": + version "1.0.6" + resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz" + integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== + +"@types/graceful-fs@^4.1.3": + version "4.1.9" + resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz" + integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== + dependencies: + "@types/node" "*" + +"@types/hast@^3.0.0": + version "3.0.4" + resolved "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz" + integrity sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ== + dependencies: + "@types/unist" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.6" + resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + +"@types/istanbul-lib-report@*": + version "3.0.3" + resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.4" + resolved "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@29.5.14": + version "29.5.14" + resolved "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz" + integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + +"@types/jsdom@^20.0.0": + version "20.0.1" + resolved "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz" + integrity sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ== + dependencies: + "@types/node" "*" + "@types/tough-cookie" "*" + parse5 "^7.0.0" + +"@types/json-schema@*", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.9": + version "7.0.15" + resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" + integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== + +"@types/lodash@4.17.17": + version "4.17.17" + resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.17.tgz" + integrity sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ== + +"@types/luxon@3.6.2": + version "3.6.2" + resolved "https://registry.npmjs.org/@types/luxon/-/luxon-3.6.2.tgz" + integrity sha512-R/BdP7OxEMc44l2Ex5lSXHoIXTB2JLNa3y2QISIbr58U/YcsffyQrYW//hZSdrfxrjRZj3GcUoxMPGdO8gSYuw== + +"@types/mdast@^4.0.0": + version "4.0.4" + resolved "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz" + integrity sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA== + dependencies: + "@types/unist" "*" + +"@types/minimatch@^3.0.3": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" + integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== + +"@types/ms@*": + version "2.1.0" + resolved "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz" + integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== + +"@types/mysql@2.15.26": + version "2.15.26" + resolved "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.26.tgz" + integrity sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ== + dependencies: + "@types/node" "*" + +"@types/node@*", "@types/node@22.10.7", "@types/node@22.15.29": + version "22.15.29" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.29.tgz#c75999124a8224a3f79dd8b6ccfb37d74098f678" + integrity sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ== + dependencies: + undici-types "~6.21.0" + +"@types/parse-json@^4.0.0": + version "4.0.2" + resolved "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz" + integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== + +"@types/pdf-parse@1.1.5": + version "1.1.5" + resolved "https://registry.yarnpkg.com/@types/pdf-parse/-/pdf-parse-1.1.5.tgz#a0959022604457169177622b512ed03b975f10e2" + integrity sha512-kBfrSXsloMnUJOKi25s3+hRmkycHfLK6A09eRGqF/N8BkQoPUmaCr+q8Cli5FnfohEz/rsv82zAiPz/LXtOGhA== + dependencies: + "@types/node" "*" + +"@types/pg-pool@2.0.6": + version "2.0.6" + resolved "https://registry.npmjs.org/@types/pg-pool/-/pg-pool-2.0.6.tgz" + integrity sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ== + dependencies: + "@types/pg" "*" + +"@types/pg@*", "@types/pg@8.6.1": + version "8.6.1" + resolved "https://registry.npmjs.org/@types/pg/-/pg-8.6.1.tgz" + integrity sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w== + dependencies: + "@types/node" "*" + pg-protocol "*" + pg-types "^2.2.0" + +"@types/raf@^3.4.0": + version "3.4.3" + resolved "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz" + integrity sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw== + +"@types/react-dom@*", "@types/react-dom@19.1.6": + version "19.1.6" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.1.6.tgz#4af629da0e9f9c0f506fc4d1caa610399c595d64" + integrity sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw== + +"@types/react-modal@3.16.3": + version "3.16.3" + resolved "https://registry.npmjs.org/@types/react-modal/-/react-modal-3.16.3.tgz" + integrity sha512-xXuGavyEGaFQDgBv4UVm8/ZsG+qxeQ7f77yNrW3n+1J6XAstUy5rYHeIHPh1KzsGc6IkCIdu6lQ2xWzu1jBTLg== + dependencies: + "@types/react" "*" + +"@types/react-transition-group@^4.4.0": + version "4.4.12" + resolved "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz" + integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w== + +"@types/react@*", "@types/react@19.1.6": + version "19.1.6" + resolved "https://registry.yarnpkg.com/@types/react/-/react-19.1.6.tgz#dee39f3e1e9a7d693f156a5840570b6d57f325ea" + integrity sha512-JeG0rEWak0N6Itr6QUx+X60uQmN+5t3j9r/OVDtWzFXKaj6kD1BwJzOksD0FF6iWxZlbE1kB0q9vtnU2ekqa1Q== + dependencies: + csstype "^3.0.2" + +"@types/resolve@1.20.2": + version "1.20.2" + resolved "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz" + integrity sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q== + +"@types/shimmer@^1.2.0": + version "1.2.0" + resolved "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.2.0.tgz" + integrity sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg== + +"@types/stack-utils@^2.0.0": + version "2.0.3" + resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + +"@types/stylis@4.2.5": + version "4.2.5" + resolved "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz" + integrity sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw== + +"@types/symlink-or-copy@^1.2.0": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@types/symlink-or-copy/-/symlink-or-copy-1.2.2.tgz#51b1c00b516a5774ada5d611e65eb123f988ef8d" + integrity sha512-MQ1AnmTLOncwEf9IVU+B2e4Hchrku5N67NkgcAHW0p3sdzPe0FNMANxEm6OJUzPniEQGkeT3OROLlCwZJLWFZA== + +"@types/tedious@^4.0.14": + version "4.0.14" + resolved "https://registry.npmjs.org/@types/tedious/-/tedious-4.0.14.tgz" + integrity sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw== + dependencies: + "@types/node" "*" + +"@types/tough-cookie@*": + version "4.0.5" + resolved "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz" + integrity sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA== + +"@types/trusted-types@^2.0.2": + version "2.0.7" + resolved "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz" + integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw== + +"@types/unist@*", "@types/unist@^3.0.0": + version "3.0.3" + resolved "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz" + integrity sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q== + +"@types/unist@^2.0.0": + version "2.0.11" + resolved "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz" + integrity sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA== + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.8": + version "17.0.33" + resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz" + integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@*", "@typescript-eslint/eslint-plugin@8.33.1", "@typescript-eslint/eslint-plugin@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0": + version "8.33.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.1.tgz#532641b416ed2afd5be893cddb2a58e9cd1f7a3e" + integrity sha512-TDCXj+YxLgtvxvFlAvpoRv9MAncDLBV2oT9Bd7YBGC/b/sEURoOYuIwLI99rjWOfY3QtDzO+mk0n4AmdFExW8A== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "8.33.1" + "@typescript-eslint/type-utils" "8.33.1" + "@typescript-eslint/utils" "8.33.1" + "@typescript-eslint/visitor-keys" "8.33.1" + graphemer "^1.4.0" + ignore "^7.0.0" + natural-compare "^1.4.0" + ts-api-utils "^2.1.0" + +"@typescript-eslint/parser@*", "@typescript-eslint/parser@8.33.1", "@typescript-eslint/parser@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0": + version "8.33.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.33.1.tgz#ef9a5ee6aa37a6b4f46cc36d08a14f828238afe2" + integrity sha512-qwxv6dq682yVvgKKp2qWwLgRbscDAYktPptK4JPojCwwi3R9cwrvIxS4lvBpzmcqzR4bdn54Z0IG1uHFskW4dA== + dependencies: + "@typescript-eslint/scope-manager" "8.33.1" + "@typescript-eslint/types" "8.33.1" + "@typescript-eslint/typescript-estree" "8.33.1" + "@typescript-eslint/visitor-keys" "8.33.1" + debug "^4.3.4" + +"@typescript-eslint/project-service@8.33.1": + version "8.33.1" + resolved "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.1.tgz" + integrity sha512-DZR0efeNklDIHHGRpMpR5gJITQpu6tLr9lDJnKdONTC7vvzOlLAG/wcfxcdxEWrbiZApcoBCzXqU/Z458Za5Iw== + dependencies: + "@typescript-eslint/tsconfig-utils" "^8.33.1" + "@typescript-eslint/types" "^8.33.1" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@8.33.1", "@typescript-eslint/scope-manager@^8.15.0": + version "8.33.1" + resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.1.tgz" + integrity sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA== + dependencies: + "@typescript-eslint/types" "8.33.1" + "@typescript-eslint/visitor-keys" "8.33.1" + +"@typescript-eslint/tsconfig-utils@8.33.1", "@typescript-eslint/tsconfig-utils@^8.33.1": + version "8.33.1" + resolved "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.1.tgz" + integrity sha512-STAQsGYbHCF0/e+ShUQ4EatXQ7ceh3fBCXkNU7/MZVKulrlq1usH7t2FhxvCpuCi5O5oi1vmVaAjrGeL71OK1g== + +"@typescript-eslint/type-utils@8.33.1": + version "8.33.1" + resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.33.1.tgz" + integrity sha512-1cG37d9xOkhlykom55WVwG2QRNC7YXlxMaMzqw2uPeJixBFfKWZgaP/hjAObqMN/u3fr5BrTwTnc31/L9jQ2ww== + dependencies: + "@typescript-eslint/typescript-estree" "8.33.1" + "@typescript-eslint/utils" "8.33.1" + debug "^4.3.4" + ts-api-utils "^2.1.0" + +"@typescript-eslint/types@8.33.1", "@typescript-eslint/types@^8.33.1": + version "8.33.1" + resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.1.tgz" + integrity sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg== + +"@typescript-eslint/typescript-estree@8.33.1": + version "8.33.1" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.1.tgz" + integrity sha512-+s9LYcT8LWjdYWu7IWs7FvUxpQ/DGkdjZeE/GGulHvv8rvYwQvVaUZ6DE+j5x/prADUgSbbCWZ2nPI3usuVeOA== + dependencies: + "@typescript-eslint/project-service" "8.33.1" + "@typescript-eslint/tsconfig-utils" "8.33.1" + "@typescript-eslint/types" "8.33.1" + "@typescript-eslint/visitor-keys" "8.33.1" + debug "^4.3.4" + fast-glob "^3.3.2" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^2.1.0" + +"@typescript-eslint/utils@8.33.1", "@typescript-eslint/utils@^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/utils@^8.15.0", "@typescript-eslint/utils@^8.18.1": + version "8.33.1" + resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.33.1.tgz" + integrity sha512-52HaBiEQUaRYqAXpfzWSR2U3gxk92Kw006+xZpElaPMg3C4PgM+A5LqwoQI1f9E5aZ/qlxAZxzm42WX+vn92SQ== + dependencies: + "@eslint-community/eslint-utils" "^4.7.0" + "@typescript-eslint/scope-manager" "8.33.1" + "@typescript-eslint/types" "8.33.1" + "@typescript-eslint/typescript-estree" "8.33.1" + +"@typescript-eslint/visitor-keys@8.33.1": + version "8.33.1" + resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.1.tgz" + integrity sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ== + dependencies: + "@typescript-eslint/types" "8.33.1" + eslint-visitor-keys "^4.2.0" + +"@ungap/structured-clone@^1.0.0", "@ungap/structured-clone@^1.2.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" + integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== + +"@unrs/resolver-binding-darwin-arm64@1.7.2": + version "1.7.2" + resolved "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.7.2.tgz" + integrity sha512-vxtBno4xvowwNmO/ASL0Y45TpHqmNkAaDtz4Jqb+clmcVSSl8XCG/PNFFkGsXXXS6AMjP+ja/TtNCFFa1QwLRg== + +"@unrs/resolver-binding-darwin-x64@1.7.2": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.7.2.tgz#97e0212a85c56e156a272628ec55da7aff992161" + integrity sha512-qhVa8ozu92C23Hsmv0BF4+5Dyyd5STT1FolV4whNgbY6mj3kA0qsrGPe35zNR3wAN7eFict3s4Rc2dDTPBTuFQ== + +"@unrs/resolver-binding-freebsd-x64@1.7.2": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.7.2.tgz#07594a9d1d83e84b52908800459273ea00caf595" + integrity sha512-zKKdm2uMXqLFX6Ac7K5ElnnG5VIXbDlFWzg4WJ8CGUedJryM5A3cTgHuGMw1+P5ziV8CRhnSEgOnurTI4vpHpg== + +"@unrs/resolver-binding-linux-arm-gnueabihf@1.7.2": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.7.2.tgz#9ef6031bb1136ee7862a6f94a2a53c395d2b6fae" + integrity sha512-8N1z1TbPnHH+iDS/42GJ0bMPLiGK+cUqOhNbMKtWJ4oFGzqSJk/zoXFzcQkgtI63qMcUI7wW1tq2usZQSb2jxw== + +"@unrs/resolver-binding-linux-arm-musleabihf@1.7.2": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.7.2.tgz#24910379ab39da1b15d65b1a06b4bfb4c293ca0c" + integrity sha512-tjYzI9LcAXR9MYd9rO45m1s0B/6bJNuZ6jeOxo1pq1K6OBuRMMmfyvJYval3s9FPPGmrldYA3mi4gWDlWuTFGA== + +"@unrs/resolver-binding-linux-arm64-gnu@1.7.2": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.7.2.tgz#49b6a8fb8f42f7530f51bc2e60fc582daed31ffb" + integrity sha512-jon9M7DKRLGZ9VYSkFMflvNqu9hDtOCEnO2QAryFWgT6o6AXU8du56V7YqnaLKr6rAbZBWYsYpikF226v423QA== + +"@unrs/resolver-binding-linux-arm64-musl@1.7.2": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.7.2.tgz#3a9707a6afda534f30c8de8a5de6c193b1b6d164" + integrity sha512-c8Cg4/h+kQ63pL43wBNaVMmOjXI/X62wQmru51qjfTvI7kmCy5uHTJvK/9LrF0G8Jdx8r34d019P1DVJmhXQpA== + +"@unrs/resolver-binding-linux-ppc64-gnu@1.7.2": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.7.2.tgz#659831ff2bfe8157d806b69b6efe142265bf9f0f" + integrity sha512-A+lcwRFyrjeJmv3JJvhz5NbcCkLQL6Mk16kHTNm6/aGNc4FwPHPE4DR9DwuCvCnVHvF5IAd9U4VIs/VvVir5lg== + +"@unrs/resolver-binding-linux-riscv64-gnu@1.7.2": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.7.2.tgz#e75abebd53cdddb3d635f6efb7a5ef6e96695717" + integrity sha512-hQQ4TJQrSQW8JlPm7tRpXN8OCNP9ez7PajJNjRD1ZTHQAy685OYqPrKjfaMw/8LiHCt8AZ74rfUVHP9vn0N69Q== + +"@unrs/resolver-binding-linux-riscv64-musl@1.7.2": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.7.2.tgz#e99b5316ee612b180aff5a7211717f3fc8c3e54e" + integrity sha512-NoAGbiqrxtY8kVooZ24i70CjLDlUFI7nDj3I9y54U94p+3kPxwd2L692YsdLa+cqQ0VoqMWoehDFp21PKRUoIQ== + +"@unrs/resolver-binding-linux-s390x-gnu@1.7.2": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.7.2.tgz#36646d5f60246f0eae650fc7bcd79b3cbf7dcff1" + integrity sha512-KaZByo8xuQZbUhhreBTW+yUnOIHUsv04P8lKjQ5otiGoSJ17ISGYArc+4vKdLEpGaLbemGzr4ZeUbYQQsLWFjA== + +"@unrs/resolver-binding-linux-x64-gnu@1.7.2": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.7.2.tgz#e720adc2979702c62f4040de05c854f186268c27" + integrity sha512-dEidzJDubxxhUCBJ/SHSMJD/9q7JkyfBMT77Px1npl4xpg9t0POLvnWywSk66BgZS/b2Hy9Y1yFaoMTFJUe9yg== + +"@unrs/resolver-binding-linux-x64-musl@1.7.2": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.7.2.tgz#684e576557d20deb4ac8ea056dcbe79739ca2870" + integrity sha512-RvP+Ux3wDjmnZDT4XWFfNBRVG0fMsc+yVzNFUqOflnDfZ9OYujv6nkh+GOr+watwrW4wdp6ASfG/e7bkDradsw== + +"@unrs/resolver-binding-wasm32-wasi@1.7.2": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.7.2.tgz#5b138ce8d471f5d0c8d6bfab525c53b80ca734e0" + integrity sha512-y797JBmO9IsvXVRCKDXOxjyAE4+CcZpla2GSoBQ33TVb3ILXuFnMrbR/QQZoauBYeOFuu4w3ifWLw52sdHGz6g== + dependencies: + "@napi-rs/wasm-runtime" "^0.2.9" + +"@unrs/resolver-binding-win32-arm64-msvc@1.7.2": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.7.2.tgz#bd772db4e8a02c31161cf1dfa33852eb7ef22df6" + integrity sha512-gtYTh4/VREVSLA+gHrfbWxaMO/00y+34htY7XpioBTy56YN2eBjkPrY1ML1Zys89X3RJDKVaogzwxlM1qU7egg== + +"@unrs/resolver-binding-win32-ia32-msvc@1.7.2": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.7.2.tgz#a6955ccdc43e809a158c4fe2d54931d34c3f7b51" + integrity sha512-Ywv20XHvHTDRQs12jd3MY8X5C8KLjDbg/jyaal/QLKx3fAShhJyD4blEANInsjxW3P7isHx1Blt56iUDDJO3jg== + +"@unrs/resolver-binding-win32-x64-msvc@1.7.2": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.7.2.tgz#7fd81d89e34a711d398ca87f6d5842735d49721e" + integrity sha512-friS8NEQfHaDbkThxopGk+LuE5v3iY0StruifjQEt7SLbA46OnfgMO15sOTkbpJkol6RB+1l1TYPXh0sCddpvA== + +"@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.14.1": + version "1.14.1" + resolved "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz" + integrity sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ== + dependencies: + "@webassemblyjs/helper-numbers" "1.13.2" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + +"@webassemblyjs/floating-point-hex-parser@1.13.2": + version "1.13.2" + resolved "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz" + integrity sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA== + +"@webassemblyjs/helper-api-error@1.13.2": + version "1.13.2" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz" + integrity sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ== + +"@webassemblyjs/helper-buffer@1.14.1": + version "1.14.1" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz" + integrity sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA== + +"@webassemblyjs/helper-numbers@1.13.2": + version "1.13.2" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz" + integrity sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.13.2" + "@webassemblyjs/helper-api-error" "1.13.2" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/helper-wasm-bytecode@1.13.2": + version "1.13.2" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz" + integrity sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA== + +"@webassemblyjs/helper-wasm-section@1.14.1": + version "1.14.1" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz" + integrity sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-buffer" "1.14.1" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/wasm-gen" "1.14.1" + +"@webassemblyjs/ieee754@1.13.2": + version "1.13.2" + resolved "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz" + integrity sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.13.2": + version "1.13.2" + resolved "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz" + integrity sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.13.2": + version "1.13.2" + resolved "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz" + integrity sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ== + +"@webassemblyjs/wasm-edit@^1.14.1": + version "1.14.1" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz" + integrity sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-buffer" "1.14.1" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/helper-wasm-section" "1.14.1" + "@webassemblyjs/wasm-gen" "1.14.1" + "@webassemblyjs/wasm-opt" "1.14.1" + "@webassemblyjs/wasm-parser" "1.14.1" + "@webassemblyjs/wast-printer" "1.14.1" + +"@webassemblyjs/wasm-gen@1.14.1": + version "1.14.1" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz" + integrity sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/ieee754" "1.13.2" + "@webassemblyjs/leb128" "1.13.2" + "@webassemblyjs/utf8" "1.13.2" + +"@webassemblyjs/wasm-opt@1.14.1": + version "1.14.1" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz" + integrity sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-buffer" "1.14.1" + "@webassemblyjs/wasm-gen" "1.14.1" + "@webassemblyjs/wasm-parser" "1.14.1" + +"@webassemblyjs/wasm-parser@1.14.1", "@webassemblyjs/wasm-parser@^1.14.1": + version "1.14.1" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz" + integrity sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-api-error" "1.13.2" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/ieee754" "1.13.2" + "@webassemblyjs/leb128" "1.13.2" + "@webassemblyjs/utf8" "1.13.2" + +"@webassemblyjs/wast-printer@1.14.1": + version "1.14.1" + resolved "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz" + integrity sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@xtuc/long" "4.2.2" + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + +abab@^2.0.6: + version "2.0.6" + resolved "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz" + integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== + +abs-svg-path@^0.1.1: + version "0.1.1" + resolved "https://registry.npmjs.org/abs-svg-path/-/abs-svg-path-0.1.1.tgz" + integrity sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA== + +acorn-globals@^7.0.0: + version "7.0.1" + resolved "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz" + integrity sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q== + dependencies: + acorn "^8.1.0" + acorn-walk "^8.0.2" + +acorn-import-attributes@^1.9.5: + version "1.9.5" + resolved "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz" + integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-walk@^8.0.2, acorn-walk@^8.1.1: + version "8.3.4" + resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz" + integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== + dependencies: + acorn "^8.11.0" + +acorn@^8.1.0, acorn@^8.11.0, acorn@^8.14.0, acorn@^8.4.1, acorn@^8.8.1, acorn@^8.8.2, acorn@^8.9.0: + version "8.15.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" + integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== + +agent-base@6: + version "6.0.2" + resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +ajv-formats@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== + dependencies: + ajv "^8.0.0" + +ajv-keywords@^5.1.0: + version "5.1.0" + resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz" + integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== + dependencies: + fast-deep-equal "^3.1.3" + +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" + 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.0.1, ajv@^8.6.0, ajv@^8.9.0: + version "8.17.1" + resolved "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" + integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +ansi-styles@^6.2.1: + 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.0.3, anymatch@^3.1.3, anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +aria-hidden@^1.2.4: + version "1.2.4" + resolved "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz" + integrity sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A== + dependencies: + tslib "^2.0.0" + +aria-query@5.3.0, aria-query@^5.0.0: + version "5.3.0" + resolved "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz" + integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== + dependencies: + dequal "^2.0.3" + +aria-query@^5.3.2: + version "5.3.2" + resolved "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz" + integrity sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw== + +array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz" + integrity sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw== + dependencies: + call-bound "^1.0.3" + is-array-buffer "^3.0.5" + +array-includes@^3.1.6, array-includes@^3.1.8: + version "3.1.8" + resolved "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz" + integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.4" + is-string "^1.0.7" + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +array.prototype.findlast@^1.2.5: + version "1.2.5" + resolved "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz" + integrity sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-shim-unscopables "^1.0.2" + +array.prototype.findlastindex@^1.2.5: + version "1.2.6" + resolved "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz" + integrity sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.4" + define-properties "^1.2.1" + es-abstract "^1.23.9" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + es-shim-unscopables "^1.1.0" + +array.prototype.flat@^1.3.1, array.prototype.flat@^1.3.2: + version "1.3.3" + resolved "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz" + integrity sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-shim-unscopables "^1.0.2" + +array.prototype.flatmap@^1.3.2, array.prototype.flatmap@^1.3.3: + version "1.3.3" + resolved "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz" + integrity sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-shim-unscopables "^1.0.2" + +array.prototype.tosorted@^1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz" + integrity sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.3" + es-errors "^1.3.0" + es-shim-unscopables "^1.0.2" + +arraybuffer.prototype.slice@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz" + integrity sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ== + dependencies: + array-buffer-byte-length "^1.0.1" + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + is-array-buffer "^3.0.4" + +ast-types-flow@^0.0.8: + version "0.0.8" + resolved "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz" + integrity sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ== + +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + +async-function@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz" + integrity sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA== + +async@^3.2.3: + version "3.2.6" + resolved "https://registry.npmjs.org/async/-/async-3.2.6.tgz" + integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + +available-typed-arrays@^1.0.7: + version "1.0.7" + resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz" + integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== + dependencies: + possible-typed-array-names "^1.0.0" + +axe-core@^4.10.0: + version "4.10.3" + resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz" + integrity sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg== + +axobject-query@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz" + integrity sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ== + +b4a@^1.6.4: + version "1.6.7" + resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.7.tgz#a99587d4ebbfbd5a6e3b21bdb5d5fa385767abe4" + integrity sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg== + +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== + dependencies: + "@jest/transform" "^29.7.0" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.6.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-plugin-macros@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz" + integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== + dependencies: + "@babel/runtime" "^7.12.5" + cosmiconfig "^7.0.0" + resolve "^1.19.0" + +babel-plugin-polyfill-corejs2@^0.4.10: + version "0.4.13" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.13.tgz" + integrity sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g== + dependencies: + "@babel/compat-data" "^7.22.6" + "@babel/helper-define-polyfill-provider" "^0.6.4" + semver "^6.3.1" + +babel-plugin-polyfill-corejs3@^0.11.0: + version "0.11.1" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz" + integrity sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.3" + core-js-compat "^3.40.0" + +babel-plugin-polyfill-regenerator@^0.6.1: + version "0.6.4" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.4.tgz" + integrity sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.4" + +babel-preset-current-node-syntax@^1.0.0: + version "1.1.0" + resolved "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz" + integrity sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@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-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== + dependencies: + babel-plugin-jest-hoist "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + +bail@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz" + integrity sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +balanced-match@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz" + integrity sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA== + +bare-events@^2.2.0: + version "2.5.4" + resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.5.4.tgz#16143d435e1ed9eafd1ab85f12b89b3357a41745" + integrity sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA== + +base64-js@0.0.8: + version "0.0.8" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz" + integrity sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw== + +base64-js@^1.1.2, base64-js@^1.3.0, base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bidi-js@^1.0.2: + version "1.0.3" + resolved "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz" + integrity sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw== + dependencies: + require-from-string "^2.0.2" + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +bl@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-5.1.0.tgz#183715f678c7188ecef9fe475d90209400624273" + integrity sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ== + dependencies: + buffer "^6.0.3" + inherits "^2.0.4" + readable-stream "^3.4.0" + +bluebird@^3.4.1: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz" + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + +brace-expansion@^1.1.7: + version "1.1.12" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz" + integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.2" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz" + integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3, braces@~3.0.2: + version "3.0.3" + resolved "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +broccoli-node-api@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/broccoli-node-api/-/broccoli-node-api-1.7.0.tgz#391aa6edecd2a42c63c111b4162956b2fa288cb6" + integrity sha512-QIqLSVJWJUVOhclmkmypJJH9u9s/aWH4+FH6Q6Ju5l+Io4dtwqdPUNmDfw40o6sxhbZHhqGujDJuHTML1wG8Yw== + +broccoli-node-info@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/broccoli-node-info/-/broccoli-node-info-2.2.0.tgz#feb01c13020792f429e01d7f7845dc5b3a7932b3" + integrity sha512-VabSGRpKIzpmC+r+tJueCE5h8k6vON7EIMMWu6d/FyPdtijwLQ7QvzShEw+m3mHoDzUaj/kiZsDYrS8X2adsBg== + +broccoli-output-wrapper@^3.2.5: + version "3.2.5" + resolved "https://registry.yarnpkg.com/broccoli-output-wrapper/-/broccoli-output-wrapper-3.2.5.tgz#514b17801c92922a2c2f87fd145df2a25a11bc5f" + integrity sha512-bQAtwjSrF4Nu0CK0JOy5OZqw9t5U0zzv2555EA/cF8/a8SLDTIetk9UgrtMVw7qKLKdSpOZ2liZNeZZDaKgayw== + dependencies: + fs-extra "^8.1.0" + heimdalljs-logger "^0.1.10" + symlink-or-copy "^1.2.0" + +broccoli-plugin@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/broccoli-plugin/-/broccoli-plugin-4.0.7.tgz#dd176a85efe915ed557d913744b181abe05047db" + integrity sha512-a4zUsWtA1uns1K7p9rExYVYG99rdKeGRymW0qOCNkvDPHQxVi3yVyJHhQbM3EZwdt2E0mnhr5e0c/bPpJ7p3Wg== + dependencies: + broccoli-node-api "^1.7.0" + broccoli-output-wrapper "^3.2.5" + fs-merger "^3.2.1" + promise-map-series "^0.3.0" + quick-temp "^0.1.8" + rimraf "^3.0.2" + symlink-or-copy "^1.3.1" + +brotli@^1.3.2: + version "1.3.3" + resolved "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz" + integrity sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg== + dependencies: + base64-js "^1.1.2" + +browserify-zlib@^0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz" + integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== + dependencies: + pako "~1.0.5" + +browserslist@^4.24.0, browserslist@^4.24.4: + version "4.24.5" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz" + integrity sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw== + dependencies: + caniuse-lite "^1.0.30001716" + electron-to-chromium "^1.5.149" + node-releases "^2.0.19" + update-browserslist-db "^1.1.3" + +bs-logger@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + +busboy@1.6.0: + version "1.6.0" + resolved "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz" + integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== + dependencies: + streamsearch "^1.1.0" + +cacheable@^1.9.0: + version "1.9.0" + resolved "https://registry.npmjs.org/cacheable/-/cacheable-1.9.0.tgz" + integrity sha512-8D5htMCxPDUULux9gFzv30f04Xo3wCnik0oOxKoRTPIBoqA7HtOcJ87uBhQTs3jCfZZTrUBGsYIZOgE0ZRgMAg== + dependencies: + hookified "^1.8.2" + keyv "^5.3.3" + +call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + +call-bind@^1.0.7, call-bind@^1.0.8: + version "1.0.8" + resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz" + integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== + dependencies: + call-bind-apply-helpers "^1.0.0" + es-define-property "^1.0.0" + get-intrinsic "^1.2.4" + set-function-length "^1.2.2" + +call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +camelize@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz" + integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ== + +caniuse-lite@^1.0.30001579, caniuse-lite@^1.0.30001716: + version "1.0.30001718" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz" + integrity sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw== + +canvg@4.0.3: + version "4.0.3" + resolved "https://registry.npmjs.org/canvg/-/canvg-4.0.3.tgz" + integrity sha512-fKzMoMBwus3CWo1Uy8XJc4tqqn98RoRrGV6CsIkaNiQT5lOeHuMh4fOt+LXLzn2Wqtr4p/c2TOLz4xtu4oBlFA== + dependencies: + "@types/raf" "^3.4.0" + raf "^3.4.1" + rgbcolor "^1.0.1" + stackblur-canvas "^2.0.0" + svg-pathdata "^6.0.3" + +ccount@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz" + integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== + +chalk@3.0.0, chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@4.1.2, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0: + version "4.1.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +character-entities-html4@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz" + integrity sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA== + +character-entities-legacy@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz" + integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ== + +character-entities@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz" + integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ== + +character-reference-invalid@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz" + 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: + version "1.1.0" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.1.0.tgz#87b9bec6dd3696e405ea79da7d2749d8308b0953" + integrity sha512-+0hMx9eYhJvWbgpKV9hN7jg0JcwydpopZE4hgi+KvQtByZXPp04NiCWU0LzcAbP63abZckIHkTQaXVF52mX3xQ== + dependencies: + cheerio-select "^2.1.0" + dom-serializer "^2.0.0" + domhandler "^5.0.3" + domutils "^3.2.2" + encoding-sniffer "^0.2.0" + htmlparser2 "^10.0.0" + parse5 "^7.3.0" + parse5-htmlparser2-tree-adapter "^7.1.0" + parse5-parser-stream "^7.1.2" + undici "^7.10.0" + whatwg-mimetype "^4.0.0" + +chokidar@^3.5.3: + version "3.6.0" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + 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" + +chromatic@11.22.0: + version "11.22.0" + resolved "https://registry.npmjs.org/chromatic/-/chromatic-11.22.0.tgz" + integrity sha512-u1kAPR9lj9aFzsCp0iWPXBbsKgcxFU7iJO6mFbgNHGVg+YPBqiJMuvgB8EQHdNbHjk5amFnGnIz/Ww8fK3t9Hw== + +chromatic@11.28.2: + version "11.28.2" + resolved "https://registry.npmjs.org/chromatic/-/chromatic-11.28.2.tgz" + integrity sha512-aCmUPcZUs4/p9zRZdMreOoO/5JqO2DiJC3md1/vRx8dlMRcmR/YI5ZbgXZcai2absVR+6hsXZ5XiPxV2sboTuQ== + +chrome-trace-event@^1.0.2: + version "1.0.4" + resolved "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz" + integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ== + +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +cjs-module-lexer@^1.0.0, cjs-module-lexer@^1.2.2: + version "1.4.3" + resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz" + integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q== + +classnames@2.5.1: + version "2.5.1" + resolved "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz" + integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== + +client-only@0.0.1, client-only@^0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz" + integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +cliui@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-9.0.1.tgz#6f7890f386f6f1f79953adc1f78dec46fcc2d291" + integrity sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w== + dependencies: + string-width "^7.2.0" + strip-ansi "^7.1.0" + wrap-ansi "^9.0.0" + +clone@^2.1.2: + version "2.1.2" + resolved "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz" + integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== + +clsx@2.1.1, clsx@^2.0.0: + version "2.1.1" + resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz" + integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== + +cmdk@1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/cmdk/-/cmdk-1.0.4.tgz" + integrity sha512-AnsjfHyHpQ/EFeAnG216WY7A5LiYCoZzCSygiLvfXC3H3LFGCprErteUcszaVluGOhuOTbJS3jWHrSDYPBBygg== + dependencies: + "@radix-ui/react-dialog" "^1.1.2" + "@radix-ui/react-id" "^1.1.0" + "@radix-ui/react-primitive" "^2.0.0" + use-sync-external-store "^1.2.2" + +cmdk@1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz" + integrity sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg== + dependencies: + "@radix-ui/react-compose-refs" "^1.1.1" + "@radix-ui/react-dialog" "^1.1.6" + "@radix-ui/react-id" "^1.1.0" + "@radix-ui/react-primitive" "^2.0.2" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +collect-v8-coverage@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz" + integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@^1.0.0, color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-string@^1.9.0, color-string@^1.9.1: + version "1.9.1" + resolved "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz" + 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.npmjs.org/color/-/color-4.2.3.tgz" + integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== + dependencies: + color-convert "^2.0.1" + color-string "^1.9.0" + +colord@^2.9.3: + version "2.9.3" + resolved "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz" + integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== + +colors@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" + integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +comma-separated-tokens@^2.0.0: + version "2.0.3" + resolved "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz" + integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== + +commander@13.0.0: + version "13.0.0" + resolved "https://registry.npmjs.org/commander/-/commander-13.0.0.tgz" + integrity sha512-oPYleIY8wmTVzkvQq10AEok6YcTC4sRUBl8F9gVuwchGVUCTbl/vhLTaQqutuuySYOsu8YTgV+OxKc/8Yvx+mQ== + +commander@^12.1.0: + version "12.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" + integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^7.2.0: + version "7.2.0" + resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + +common-tags@^1.8.0: + version "1.8.2" + resolved "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz" + integrity sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA== + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz" + integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== + +compute-scroll-into-view@^3.1.0: + version "3.1.1" + resolved "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz" + integrity sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +convert-source-map@^1.5.0: + version "1.9.0" + resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +convert-stream@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/convert-stream/-/convert-stream-1.0.2.tgz#152a7f10f4635e2bd000425b1fd025d1f114ff4d" + integrity sha512-kPDydAkRaSi3AfhRFRizZldlFucJTDw+zOtYVjQVePVGiGGhcNaBwisbUMRCozoJlZ4GOGgqVrjJsah9xC1wYQ== + dependencies: + bluebird "^3.4.1" + +core-js-compat@^3.40.0: + version "3.42.0" + resolved "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.42.0.tgz" + integrity sha512-bQasjMfyDGyaeWKBIu33lHh9qlSR0MFE/Nmc6nMjf/iU9b3rSMdAYz1Baxrv4lPdGUsTqZudHA4jIGSJy0SWZQ== + dependencies: + browserslist "^4.24.4" + +core-js@^3.0.0, core-js@^3.38.1: + version "3.42.0" + resolved "https://registry.npmjs.org/core-js/-/core-js-3.42.0.tgz" + integrity sha512-Sz4PP4ZA+Rq4II21qkNqOEDTDrCvcANId3xpIgB34NDkWc3UduWj2dqEtN9yZIq8Dk3HyPI33x9sqqU5C8sr0g== + +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@^7.0.0: + version "7.1.0" + resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz" + 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.1.3: + version "8.3.6" + resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz" + integrity sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA== + dependencies: + import-fresh "^3.3.0" + js-yaml "^4.1.0" + parse-json "^5.2.0" + path-type "^4.0.0" + +cosmiconfig@^9.0.0: + version "9.0.0" + resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz" + integrity sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg== + dependencies: + env-paths "^2.2.1" + import-fresh "^3.3.0" + js-yaml "^4.1.0" + parse-json "^5.2.0" + +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz" + integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +crisp-sdk-web@1.0.25: + version "1.0.25" + resolved "https://registry.npmjs.org/crisp-sdk-web/-/crisp-sdk-web-1.0.25.tgz" + integrity sha512-CWTHFFeHRV0oqiXoPh/aIAKhFs6xcIM4NenGPnClAMCZUDQgQsF1OWmZWmnVNjJriXUmWRgDfeUxcxygS0dCRA== + +cross-env@7.0.3: + version "7.0.3" + resolved "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz" + integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== + dependencies: + cross-spawn "^7.0.1" + +cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +crypto-js@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz" + integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q== + +crypto-random-string@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz" + integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== + +css-color-keywords@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz" + integrity sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg== + +css-functions-list@^3.2.3: + version "3.2.3" + resolved "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.3.tgz" + integrity sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA== + +css-select@^5.1.0: + version "5.1.0" + resolved "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz" + 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-to-react-native@3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz" + integrity sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ== + dependencies: + camelize "^1.0.0" + css-color-keywords "^1.0.0" + postcss-value-parser "^4.0.2" + +css-tree@^2.3.1: + version "2.3.1" + resolved "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz" + integrity sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw== + dependencies: + mdn-data "2.0.30" + source-map-js "^1.0.1" + +css-tree@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz" + integrity sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w== + dependencies: + mdn-data "2.12.2" + source-map-js "^1.0.1" + +css-tree@~2.2.0: + version "2.2.1" + resolved "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz" + integrity sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA== + dependencies: + mdn-data "2.0.28" + source-map-js "^1.0.1" + +css-what@^6.1.0: + version "6.1.0" + resolved "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz" + integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== + +css.escape@^1.5.1: + version "1.5.1" + resolved "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz" + integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +csso@^5.0.5: + version "5.0.5" + resolved "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz" + integrity sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ== + dependencies: + css-tree "~2.2.0" + +cssom@^0.5.0: + version "0.5.0" + resolved "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz" + integrity sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw== + +cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssstyle@^2.3.0: + version "2.3.0" + resolved "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz" + integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== + dependencies: + cssom "~0.3.6" + +csstype@3.1.3, csstype@^3.0.2: + version "3.1.3" + resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + +damerau-levenshtein@^1.0.8: + version "1.0.8" + resolved "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz" + integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== + +data-urls@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz" + integrity sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ== + dependencies: + abab "^2.0.6" + whatwg-mimetype "^3.0.0" + whatwg-url "^11.0.0" + +data-view-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz" + integrity sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-data-view "^1.0.2" + +data-view-byte-length@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz" + integrity sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-data-view "^1.0.2" + +data-view-byte-offset@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz" + integrity sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5, debug@^4.4.0, debug@^4.4.1: + version "4.4.1" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz" + integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== + dependencies: + ms "^2.1.3" + +debug@^2.2.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@^3.1.0, debug@^3.2.7: + version "3.2.7" + resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +decimal.js@^10.4.2, decimal.js@^10.4.3: + version "10.5.0" + resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz" + integrity sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw== + +decode-named-character-reference@^1.0.0: + version "1.2.0" + resolved "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz" + integrity sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q== + dependencies: + character-entities "^2.0.0" + +dedent@^1.0.0: + version "1.6.0" + resolved "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz" + integrity sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA== + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +deepmerge@4.3.1, deepmerge@^4.2.2, deepmerge@^4.3.1: + version "4.3.1" + resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +define-data-property@^1.0.1, define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +define-properties@^1.1.3, define-properties@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +dequal@^2.0.0, dequal@^2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== + +detect-libc@^2.0.3: + version "2.0.4" + resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz" + integrity sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA== + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +detect-node-es@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz" + integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== + +devlop@^1.0.0, devlop@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz" + integrity sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA== + dependencies: + dequal "^2.0.0" + +dfa@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz" + integrity sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q== + +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +dnd-core@14.0.1: + version "14.0.1" + resolved "https://registry.npmjs.org/dnd-core/-/dnd-core-14.0.1.tgz" + integrity sha512-+PVS2VPTgKFPYWo3vAFEA8WPbTf7/xo43TifH9G8S1KqnrQu0o77A3unrF5yOugy4mIz7K5wAVFHUcha7wsz6A== + dependencies: + "@react-dnd/asap" "^4.0.0" + "@react-dnd/invariant" "^2.0.0" + redux "^4.1.1" + +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dom-accessibility-api@^0.5.9: + version "0.5.16" + resolved "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz" + integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== + +dom-accessibility-api@^0.6.3: + version "0.6.3" + resolved "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz" + integrity sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w== + +dom-helpers@^5.0.1: + version "5.2.1" + resolved "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + +domelementtype@^2.3.0: + version "2.3.0" + resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + +domexception@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz" + integrity sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw== + dependencies: + webidl-conversions "^7.0.0" + +domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + +domutils@^3.0.1, domutils@^3.2.1, domutils@^3.2.2: + version "3.2.2" + resolved "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz" + integrity sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw== + 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.npmjs.org/dot-case/-/dot-case-3.0.4.tgz" + integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + +dotenv@16.5.0, dotenv@^16.3.1: + version "16.5.0" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz" + integrity sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg== + +downshift@9.0.8: + version "9.0.8" + resolved "https://registry.npmjs.org/downshift/-/downshift-9.0.8.tgz" + integrity sha512-59BWD7+hSUQIM1DeNPLirNNnZIO9qMdIK5GQ/Uo8q34gT4B78RBlb9dhzgnh0HfQTJj4T/JKYD8KoLAlMWnTsA== + dependencies: + "@babel/runtime" "^7.24.5" + compute-scroll-into-view "^3.1.0" + prop-types "^15.8.1" + react-is "18.2.0" + tslib "^2.6.2" + +downshift@9.0.9: + version "9.0.9" + resolved "https://registry.npmjs.org/downshift/-/downshift-9.0.9.tgz" + integrity sha512-ygOT8blgiz5liDuEFAIaPeU4dDEa+w9p6PHVUisPIjrkF5wfR59a52HpGWAVVMoWnoFO8po2mZSScKZueihS7g== + dependencies: + "@babel/runtime" "^7.24.5" + compute-scroll-into-view "^3.1.0" + prop-types "^15.8.1" + react-is "18.2.0" + tslib "^2.6.2" + +dunder-proto@^1.0.0, dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + +ejs@^3.1.10, ejs@^3.1.6: + version "3.1.10" + resolved "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz" + integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== + dependencies: + jake "^10.8.5" + +electron-to-chromium@^1.5.149: + version "1.5.154" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.154.tgz" + integrity sha512-G4VCFAyKbp1QJ+sWdXYIRYsPGvlV5sDACfCmoMFog3rjm1syLhI41WXm/swZypwCIWIm4IFLWzHY14joWMQ5Fw== + +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== + +emoji-mart@5.6.0: + version "5.6.0" + resolved "https://registry.npmjs.org/emoji-mart/-/emoji-mart-5.6.0.tgz" + integrity sha512-eJp3QRe79pjwa+duv+n7+5YsNhRcMl812EcFVwrnRvYKoNPoQb5qxU8DG6Bgwji0akHdp6D4Ln6tYLG58MFSow== + +emoji-regex@^10.3.0: + version "10.4.0" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz" + integrity sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +encoding-sniffer@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz#396ec97ac22ce5a037ba44af1992ac9d46a7b819" + integrity sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw== + dependencies: + iconv-lite "^0.6.3" + whatwg-encoding "^3.1.1" + +enhanced-resolve@^5.17.1: + version "5.18.1" + resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz" + integrity sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +ensure-posix-path@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ensure-posix-path/-/ensure-posix-path-1.1.1.tgz#3c62bdb19fa4681544289edb2b382adc029179ce" + integrity sha512-VWU0/zXzVbeJNXvME/5EmLuEj2TauvoaTz6aFYK1Z92JCBlDlZ3Gu0tuGR42kpW1754ywTs+QB0g5TP0oj9Zaw== + +entities@^4.2.0, entities@^4.4.0: + version "4.5.0" + resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + +entities@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz" + integrity sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw== + +env-paths@^2.2.1: + version "2.2.1" + resolved "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== + +eol@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/eol/-/eol-0.9.1.tgz#f701912f504074be35c6117a5c4ade49cd547acd" + integrity sha512-Ds/TEoZjwggRoz/Q2O7SE3i4Jm66mqTDfmdHdq/7DKVk3bro9Q8h6WdXKdPqFLMoqxrDK5SVRzHVPOS6uuGtrg== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.17.5, es-abstract@^1.23.2, es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.6, es-abstract@^1.23.9: + version "1.23.9" + resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz" + integrity sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA== + dependencies: + array-buffer-byte-length "^1.0.2" + arraybuffer.prototype.slice "^1.0.4" + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.3" + data-view-buffer "^1.0.2" + data-view-byte-length "^1.0.2" + data-view-byte-offset "^1.0.1" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-set-tostringtag "^2.1.0" + es-to-primitive "^1.3.0" + function.prototype.name "^1.1.8" + get-intrinsic "^1.2.7" + get-proto "^1.0.0" + get-symbol-description "^1.1.0" + globalthis "^1.0.4" + gopd "^1.2.0" + has-property-descriptors "^1.0.2" + has-proto "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + internal-slot "^1.1.0" + is-array-buffer "^3.0.5" + is-callable "^1.2.7" + is-data-view "^1.0.2" + is-regex "^1.2.1" + is-shared-array-buffer "^1.0.4" + is-string "^1.1.1" + is-typed-array "^1.1.15" + is-weakref "^1.1.0" + math-intrinsics "^1.1.0" + object-inspect "^1.13.3" + object-keys "^1.1.1" + object.assign "^4.1.7" + own-keys "^1.0.1" + regexp.prototype.flags "^1.5.3" + safe-array-concat "^1.1.3" + safe-push-apply "^1.0.0" + safe-regex-test "^1.1.0" + set-proto "^1.0.0" + string.prototype.trim "^1.2.10" + string.prototype.trimend "^1.0.9" + string.prototype.trimstart "^1.0.8" + typed-array-buffer "^1.0.3" + typed-array-byte-length "^1.0.3" + typed-array-byte-offset "^1.0.4" + typed-array-length "^1.0.7" + unbox-primitive "^1.1.0" + which-typed-array "^1.1.18" + +es-define-property@^1.0.0, es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-iterator-helpers@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz" + integrity sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + es-abstract "^1.23.6" + es-errors "^1.3.0" + es-set-tostringtag "^2.0.3" + function-bind "^1.1.2" + get-intrinsic "^1.2.6" + globalthis "^1.0.4" + gopd "^1.2.0" + has-property-descriptors "^1.0.2" + has-proto "^1.2.0" + has-symbols "^1.1.0" + internal-slot "^1.1.0" + iterator.prototype "^1.1.4" + safe-array-concat "^1.1.3" + +es-module-lexer@^1.2.1: + version "1.7.0" + resolved "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz" + integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== + +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.0.3, es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +es-shim-unscopables@^1.0.2, es-shim-unscopables@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz" + integrity sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw== + dependencies: + hasown "^2.0.2" + +es-to-primitive@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz" + integrity sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g== + dependencies: + is-callable "^1.2.7" + is-date-object "^1.0.5" + is-symbol "^1.0.4" + +esbuild@^0.25.0: + version "0.25.5" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.5.tgz#71075054993fdfae76c66586f9b9c1f8d7edd430" + integrity sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ== + optionalDependencies: + "@esbuild/aix-ppc64" "0.25.5" + "@esbuild/android-arm" "0.25.5" + "@esbuild/android-arm64" "0.25.5" + "@esbuild/android-x64" "0.25.5" + "@esbuild/darwin-arm64" "0.25.5" + "@esbuild/darwin-x64" "0.25.5" + "@esbuild/freebsd-arm64" "0.25.5" + "@esbuild/freebsd-x64" "0.25.5" + "@esbuild/linux-arm" "0.25.5" + "@esbuild/linux-arm64" "0.25.5" + "@esbuild/linux-ia32" "0.25.5" + "@esbuild/linux-loong64" "0.25.5" + "@esbuild/linux-mips64el" "0.25.5" + "@esbuild/linux-ppc64" "0.25.5" + "@esbuild/linux-riscv64" "0.25.5" + "@esbuild/linux-s390x" "0.25.5" + "@esbuild/linux-x64" "0.25.5" + "@esbuild/netbsd-arm64" "0.25.5" + "@esbuild/netbsd-x64" "0.25.5" + "@esbuild/openbsd-arm64" "0.25.5" + "@esbuild/openbsd-x64" "0.25.5" + "@esbuild/sunos-x64" "0.25.5" + "@esbuild/win32-arm64" "0.25.5" + "@esbuild/win32-ia32" "0.25.5" + "@esbuild/win32-x64" "0.25.5" + +escalade@^3.1.1, escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" + 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== + +escodegen@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz" + integrity sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w== + dependencies: + esprima "^4.0.1" + estraverse "^5.2.0" + esutils "^2.0.2" + optionalDependencies: + source-map "~0.6.1" + +eslint-config-next@15.3.3: + version "15.3.3" + resolved "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.3.3.tgz" + integrity sha512-QJLv/Ouk2vZnxL4b67njJwTLjTf7uZRltI0LL4GERYR4qMF5z08+gxkfODAeaK7TiC6o+cER91bDaEnwrTWV6Q== + dependencies: + "@next/eslint-plugin-next" "15.3.3" + "@rushstack/eslint-patch" "^1.10.3" + "@typescript-eslint/eslint-plugin" "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0" + "@typescript-eslint/parser" "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0" + eslint-import-resolver-node "^0.3.6" + eslint-import-resolver-typescript "^3.5.2" + eslint-plugin-import "^2.31.0" + eslint-plugin-jsx-a11y "^6.10.0" + eslint-plugin-react "^7.37.0" + eslint-plugin-react-hooks "^5.0.0" + +eslint-config-prettier@10.1.5: + version "10.1.5" + resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz" + integrity sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw== + +eslint-import-resolver-node@^0.3.6, eslint-import-resolver-node@^0.3.9: + version "0.3.9" + resolved "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz" + integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== + dependencies: + debug "^3.2.7" + is-core-module "^2.13.0" + resolve "^1.22.4" + +eslint-import-resolver-typescript@^3.5.2: + version "3.10.1" + resolved "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz" + integrity sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ== + dependencies: + "@nolyfill/is-core-module" "1.0.39" + debug "^4.4.0" + get-tsconfig "^4.10.0" + is-bun-module "^2.0.0" + stable-hash "^0.0.5" + tinyglobby "^0.2.13" + unrs-resolver "^1.6.2" + +eslint-module-utils@^2.12.0: + version "2.12.0" + resolved "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz" + integrity sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg== + dependencies: + debug "^3.2.7" + +eslint-plugin-import@2.31.0, eslint-plugin-import@^2.31.0: + version "2.31.0" + resolved "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz" + integrity sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A== + dependencies: + "@rtsao/scc" "^1.1.0" + array-includes "^3.1.8" + array.prototype.findlastindex "^1.2.5" + array.prototype.flat "^1.3.2" + array.prototype.flatmap "^1.3.2" + debug "^3.2.7" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.9" + eslint-module-utils "^2.12.0" + hasown "^2.0.2" + is-core-module "^2.15.1" + is-glob "^4.0.3" + minimatch "^3.1.2" + object.fromentries "^2.0.8" + object.groupby "^1.0.3" + object.values "^1.2.0" + semver "^6.3.1" + string.prototype.trimend "^1.0.8" + tsconfig-paths "^3.15.0" + +eslint-plugin-jest@28.12.0: + version "28.12.0" + resolved "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.12.0.tgz" + integrity sha512-J6zmDp8WiQ9tyvYXE+3RFy7/+l4hraWLzmsabYXyehkmmDd36qV4VQFc7XzcsD8C1PTNt646MSx25bO1mdd9Yw== + dependencies: + "@typescript-eslint/utils" "^6.0.0 || ^7.0.0 || ^8.0.0" + +eslint-plugin-jsx-a11y@6.10.2, eslint-plugin-jsx-a11y@^6.10.0: + version "6.10.2" + resolved "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz" + integrity sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q== + dependencies: + aria-query "^5.3.2" + array-includes "^3.1.8" + array.prototype.flatmap "^1.3.2" + ast-types-flow "^0.0.8" + axe-core "^4.10.0" + axobject-query "^4.1.0" + damerau-levenshtein "^1.0.8" + emoji-regex "^9.2.2" + hasown "^2.0.2" + jsx-ast-utils "^3.3.5" + language-tags "^1.0.9" + minimatch "^3.1.2" + object.fromentries "^2.0.8" + safe-regex-test "^1.0.3" + string.prototype.includes "^2.0.1" + +eslint-plugin-playwright@2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/eslint-plugin-playwright/-/eslint-plugin-playwright-2.2.0.tgz" + integrity sha512-qSQpAw7RcSzE3zPp8FMGkthaCWovHZ/BsXtpmnGax9vQLIovlh1bsZHEa2+j2lv9DWhnyeLM/qZmp7ffQZfQvg== + dependencies: + globals "^13.23.0" + +eslint-plugin-prettier@5.4.1: + version "5.4.1" + resolved "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.1.tgz" + integrity sha512-9dF+KuU/Ilkq27A8idRP7N2DH8iUR6qXcjF3FR2wETY21PZdBrIjwCau8oboyGj9b7etWmTGEeM8e7oOed6ZWg== + dependencies: + prettier-linter-helpers "^1.0.0" + synckit "^0.11.7" + +eslint-plugin-react-hooks@^5.0.0: + version "5.2.0" + resolved "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz" + integrity sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg== + +eslint-plugin-react@^7.37.0: + version "7.37.5" + resolved "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz" + integrity sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA== + dependencies: + array-includes "^3.1.8" + array.prototype.findlast "^1.2.5" + array.prototype.flatmap "^1.3.3" + array.prototype.tosorted "^1.1.4" + doctrine "^2.1.0" + es-iterator-helpers "^1.2.1" + estraverse "^5.3.0" + hasown "^2.0.2" + jsx-ast-utils "^2.4.1 || ^3.0.0" + minimatch "^3.1.2" + object.entries "^1.1.9" + object.fromentries "^2.0.8" + object.values "^1.2.1" + prop-types "^15.8.1" + resolve "^2.0.0-next.5" + semver "^6.3.1" + string.prototype.matchall "^4.0.12" + string.prototype.repeat "^1.0.0" + +eslint-plugin-testing-library@7.4.0: + version "7.4.0" + resolved "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-7.4.0.tgz" + integrity sha512-rmryGUowFYlljNrN/sPjSfp4uZr6gIsiTHUeFg45xNwhWzgr+izRzOanrvd28XVlVmXlBpZdJGu+aHFUBBQatA== + dependencies: + "@typescript-eslint/scope-manager" "^8.15.0" + "@typescript-eslint/utils" "^8.15.0" + +eslint-scope@5.1.1: + version "5.1.1" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint-visitor-keys@^4.2.0: + version "4.2.1" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz" + integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== + +eslint@*, eslint@8.57.0: + version "8.57.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" + integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.0" + "@humanwhocodes/config-array" "^0.11.14" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + +esprima@^4.0.0, esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.2: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: + version "5.3.0" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +estree-util-is-identifier-name@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz" + integrity sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg== + +estree-walker@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz" + integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== + +estree-walker@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +events@^3.2.0, events@^3.3.0: + version "3.3.0" + resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz" + 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" + +exenv@^1.2.0: + version "1.2.2" + resolved "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz" + integrity sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw== + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect@^29.0.0, expect@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== + dependencies: + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + +extend@^3.0.0: + version "3.0.2" + resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz" + 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.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2: + version "1.3.0" + resolved "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz" + integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== + +fast-fifo@^1.3.2: + 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.3.1: + version "3.3.1" + resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz" + 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-glob@^3.2.9, fast-glob@^3.3.2, fast-glob@^3.3.3: + version "3.3.3" + resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz" + integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== + 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.8" + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fast-uri@^3.0.1: + version "3.0.6" + resolved "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz" + integrity sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw== + +fastest-levenshtein@^1.0.16: + version "1.0.16" + resolved "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz" + integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== + +fastq@^1.13.0, fastq@^1.6.0: + version "1.19.1" + resolved "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz" + integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== + dependencies: + reusify "^1.0.4" + +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + +fdir@^6.2.0, fdir@^6.4.4: + version "6.4.4" + resolved "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz" + integrity sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg== + +fetch-mock@9.11.0: + version "9.11.0" + resolved "https://registry.npmjs.org/fetch-mock/-/fetch-mock-9.11.0.tgz" + integrity sha512-PG1XUv+x7iag5p/iNHD4/jdpxL9FtVSqRMUQhPab4hVDt80T1MH5ehzVrL2IdXO9Q2iBggArFvPqjUbHFuI58Q== + dependencies: + "@babel/core" "^7.0.0" + "@babel/runtime" "^7.0.0" + core-js "^3.0.0" + debug "^4.1.1" + glob-to-regexp "^0.4.0" + is-subset "^0.1.1" + lodash.isequal "^4.5.0" + path-to-regexp "^2.2.1" + querystring "^0.2.0" + whatwg-url "^6.5.0" + +fflate@^0.4.8: + version "0.4.8" + resolved "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz" + integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA== + +figlet@1.8.0: + version "1.8.0" + resolved "https://registry.npmjs.org/figlet/-/figlet-1.8.0.tgz" + integrity sha512-chzvGjd+Sp7KUvPHZv6EXV5Ir3Q7kYNpCr4aHrRW79qFtTefmQZNny+W1pW9kf5zeE6dikku2W50W/wAH2xWgw== + +file-entry-cache@^10.1.0: + version "10.1.0" + resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.1.0.tgz" + integrity sha512-Et/ex6smi3wOOB+n5mek+Grf7P2AxZR5ueqRUvAAn4qkyatXi3cUC1cuQXVkX0VlzBVsN4BkWJFmY/fYiRTdww== + dependencies: + flat-cache "^6.1.9" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +filelist@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== + dependencies: + minimatch "^5.0.1" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +find-root@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz" + integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flat-cache@^6.1.9: + version "6.1.9" + resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.9.tgz" + integrity sha512-DUqiKkTlAfhtl7g78IuwqYM+YqvT+as0mY+EVk6mfimy19U79pJCzDZQsnqk3Ou/T6hFXWLGbwbADzD/c8Tydg== + dependencies: + cacheable "^1.9.0" + flatted "^3.3.3" + hookified "^1.8.2" + +flatted@^3.2.9, flatted@^3.3.3: + version "3.3.3" + resolved "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz" + integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== + +fontkit@^2.0.2: + version "2.0.4" + resolved "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz" + integrity sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g== + dependencies: + "@swc/helpers" "^0.5.12" + brotli "^1.3.2" + clone "^2.1.2" + dfa "^1.2.0" + fast-deep-equal "^3.1.3" + restructure "^3.0.0" + tiny-inflate "^1.0.3" + unicode-properties "^1.4.0" + unicode-trie "^2.0.0" + +for-each@^0.3.3, for-each@^0.3.5: + version "0.3.5" + resolved "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz" + integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg== + dependencies: + is-callable "^1.2.7" + +form-data@^4.0.0: + version "4.0.2" + resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz" + integrity sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + es-set-tostringtag "^2.1.0" + mime-types "^2.1.12" + +forwarded-parse@2.1.2: + version "2.1.2" + resolved "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz" + integrity sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw== + +fs-extra@^11.2.0: + version "11.3.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.0.tgz#0daced136bbaf65a555a326719af931adc7a314d" + integrity sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-extra@^8.0.1, fs-extra@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs-extra@^9.0.1: + version "9.1.0" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz" + 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-merger@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/fs-merger/-/fs-merger-3.2.1.tgz#a225b11ae530426138294b8fbb19e82e3d4e0b3b" + integrity sha512-AN6sX12liy0JE7C2evclwoo0aCG3PFulLjrTLsJpWh/2mM+DinhpSGqYLbHBBbIW1PLRNcFhJG8Axtz8mQW3ug== + dependencies: + broccoli-node-api "^1.7.0" + broccoli-node-info "^2.1.0" + fs-extra "^8.0.1" + fs-tree-diff "^2.0.1" + walk-sync "^2.2.0" + +fs-mkdirp-stream@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fs-mkdirp-stream/-/fs-mkdirp-stream-2.0.1.tgz#1e82575c4023929ad35cf69269f84f1a8c973aa7" + integrity sha512-UTOY+59K6IA94tec8Wjqm0FSh5OVudGNB0NL/P6fB3HiE3bYOY3VYBGijsnOHNkQSwC1FKkU77pmq7xp9CskLw== + dependencies: + graceful-fs "^4.2.8" + streamx "^2.12.0" + +fs-tree-diff@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fs-tree-diff/-/fs-tree-diff-2.0.1.tgz#343e4745ab435ec39ebac5f9059ad919cd034afa" + integrity sha512-x+CfAZ/lJHQqwlD64pYM5QxWjzWhSjroaVsr8PW831zOApL55qPibed0c+xebaLWVr2BnHFoHdrwOv8pzt8R5A== + dependencies: + "@types/symlink-or-copy" "^1.2.0" + heimdalljs-logger "^0.1.7" + object-assign "^4.1.0" + path-posix "^1.0.0" + symlink-or-copy "^1.1.8" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" + 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== + +fsevents@^2.3.2, fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +function.prototype.name@^1.1.6, function.prototype.name@^1.1.8: + version "1.1.8" + resolved "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz" + integrity sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + functions-have-names "^1.2.3" + hasown "^2.0.2" + is-callable "^1.2.7" + +functions-have-names@^1.2.3: + version "1.2.3" + resolved "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-east-asian-width@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz#21b4071ee58ed04ee0db653371b55b4299875389" + integrity sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ== + +get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-nonce@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz" + integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q== + +get-own-enumerable-property-symbols@^3.0.0: + version "3.0.2" + resolved "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz" + integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-proto@^1.0.0, get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +get-symbol-description@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz" + integrity sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + +get-tsconfig@^4.10.0: + version "4.10.0" + resolved "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz" + integrity sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A== + dependencies: + resolve-pkg-maps "^1.0.0" + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob-stream@^8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-8.0.3.tgz#87e63153aadf05bd0207cde1a253ee39d91458b9" + integrity sha512-fqZVj22LtFJkHODT+M4N1RJQ3TjnnQhfE9GwZI8qXscYarnhpip70poMldRnP8ipQ/w0B621kOhfc53/J9bd/A== + dependencies: + "@gulpjs/to-absolute-glob" "^4.0.0" + anymatch "^3.1.3" + fastq "^1.13.0" + glob-parent "^6.0.2" + is-glob "^4.0.3" + is-negated-glob "^1.0.0" + normalize-path "^3.0.0" + streamx "^2.12.5" + +glob-to-regexp@^0.4.0, glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + +glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: + version "7.2.3" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" + 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" + +glob@^9.3.2: + version "9.3.5" + resolved "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz" + integrity sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q== + dependencies: + fs.realpath "^1.0.0" + minimatch "^8.0.2" + minipass "^4.2.4" + path-scurry "^1.6.1" + +global-modules@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz" + integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== + dependencies: + global-prefix "^3.0.0" + +global-prefix@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz" + 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.npmjs.org/globals/-/globals-11.12.0.tgz" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^13.19.0, globals@^13.23.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + +globalthis@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz" + integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== + dependencies: + define-properties "^1.2.1" + gopd "^1.0.1" + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz" + 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" + +globjoin@^0.1.4: + version "0.1.4" + resolved "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz" + integrity sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg== + +gopd@^1.0.1, gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.8, graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +gulp-sort@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/gulp-sort/-/gulp-sort-2.0.0.tgz#c6762a2f1f0de0a3fc595a21599d3fac8dba1aca" + integrity sha512-MyTel3FXOdh1qhw1yKhpimQrAmur9q1X0ZigLmCOxouQD+BD3za9/89O+HfbgBQvvh4igEbp0/PUWO+VqGYG1g== + dependencies: + through2 "^2.0.1" + +has-bigints@^1.0.2: + version "1.1.0" + resolved "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz" + integrity sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz" + integrity sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ== + dependencies: + dunder-proto "^1.0.0" + +has-symbols@^1.0.3, has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +hast-util-to-jsx-runtime@^2.0.0: + version "2.3.6" + resolved "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz" + integrity sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg== + 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 "^7.0.0" + space-separated-tokens "^2.0.0" + style-to-js "^1.0.0" + unist-util-position "^5.0.0" + vfile-message "^4.0.0" + +hast-util-whitespace@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz" + integrity sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw== + dependencies: + "@types/hast" "^3.0.0" + +heimdalljs-logger@^0.1.10, heimdalljs-logger@^0.1.7: + version "0.1.10" + resolved "https://registry.yarnpkg.com/heimdalljs-logger/-/heimdalljs-logger-0.1.10.tgz#90cad58aabb1590a3c7e640ddc6a4cd3a43faaf7" + integrity sha512-pO++cJbhIufVI/fmB/u2Yty3KJD0TqNPecehFae0/eps0hkZ3b4Zc/PezUMOpYuHFQbA7FxHZxa305EhmjLj4g== + dependencies: + debug "^2.2.0" + heimdalljs "^0.2.6" + +heimdalljs@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/heimdalljs/-/heimdalljs-0.2.6.tgz#b0eebabc412813aeb9542f9cc622cb58dbdcd9fe" + integrity sha512-o9bd30+5vLBvBtzCPwwGqpry2+n0Hi6H1+qwt6y+0kwRHGGF8TFIhJPmnuM0xO97zaKrDZMwO/V56fAnn8m/tA== + dependencies: + rsvp "~3.2.1" + +hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: + version "3.3.2" + resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + +hookified@^1.8.2: + version "1.9.0" + resolved "https://registry.npmjs.org/hookified/-/hookified-1.9.0.tgz" + integrity sha512-2yEEGqphImtKIe1NXWEhu6yD3hlFR4Mxk4Mtp3XEyScpSt4pQ4ymmXA1zzxZpj99QkFK+nN0nzjeb2+RUi/6CQ== + +hsl-to-hex@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/hsl-to-hex/-/hsl-to-hex-1.0.0.tgz" + integrity sha512-K6GVpucS5wFf44X0h2bLVRDsycgJmf9FF2elg+CrqD8GcFU8c6vYhgXn8NjUkFCwj+xDFb70qgLbTUm6sxwPmA== + dependencies: + hsl-to-rgb-for-reals "^1.1.0" + +hsl-to-rgb-for-reals@^1.1.0: + version "1.1.1" + resolved "https://registry.npmjs.org/hsl-to-rgb-for-reals/-/hsl-to-rgb-for-reals-1.1.1.tgz" + integrity sha512-LgOWAkrN0rFaQpfdWBQlv/VhkOxb5AsBjk6NQVx4yEzWS923T07X0M1Y0VNko2H52HeSpZrZNNMJ0aFqsdVzQg== + +html-encoding-sniffer@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz" + integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA== + dependencies: + whatwg-encoding "^2.0.0" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +html-parse-stringify@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz" + integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg== + dependencies: + void-elements "3.1.0" + +html-tags@^3.3.1: + version "3.3.1" + resolved "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz" + integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ== + +html-url-attributes@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz" + integrity sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ== + +htmlparser2@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-10.0.0.tgz#77ad249037b66bf8cc99c6e286ef73b83aeb621d" + integrity sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.2.1" + entities "^6.0.0" + +http-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz" + integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== + dependencies: + "@tootallnate/once" "2" + agent-base "6" + debug "4" + +https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +hyphen@^1.6.4: + version "1.10.6" + resolved "https://registry.npmjs.org/hyphen/-/hyphen-1.10.6.tgz" + integrity sha512-fXHXcGFTXOvZTSkPJuGOQf5Lv5T/R2itiiCVPg9LxAje5D00O0pP83yJShFq5V89Ly//Gt6acj7z8pbBr34stw== + +i18next-browser-languagedetector@8.1.0: + version "8.1.0" + resolved "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.1.0.tgz" + integrity sha512-mHZxNx1Lq09xt5kCauZ/4bsXOEA2pfpwSoU11/QTJB+pD94iONFwp+ohqi///PwiFvjFOxe1akYCdHyFo1ng5Q== + dependencies: + "@babel/runtime" "^7.23.2" + +i18next-parser@9.3.0: + version "9.3.0" + resolved "https://registry.yarnpkg.com/i18next-parser/-/i18next-parser-9.3.0.tgz#65c226cac54cd2783b59715a366be1e3515dd8e6" + integrity sha512-VaQqk/6nLzTFx1MDiCZFtzZXKKyBV6Dv0cJMFM/hOt4/BWHWRgYafzYfVQRUzotwUwjqeNCprWnutzD/YAGczg== + dependencies: + "@babel/runtime" "^7.25.0" + broccoli-plugin "^4.0.7" + cheerio "^1.0.0" + colors "^1.4.0" + commander "^12.1.0" + eol "^0.9.1" + esbuild "^0.25.0" + fs-extra "^11.2.0" + gulp-sort "^2.0.0" + i18next "^23.5.1 || ^24.2.0" + js-yaml "^4.1.0" + lilconfig "^3.1.3" + rsvp "^4.8.5" + sort-keys "^5.0.0" + typescript "^5.0.4" + vinyl "^3.0.0" + vinyl-fs "^4.0.0" + +i18next@25.2.1: + version "25.2.1" + resolved "https://registry.npmjs.org/i18next/-/i18next-25.2.1.tgz" + integrity sha512-+UoXK5wh+VlE1Zy5p6MjcvctHXAhRwQKCxiJD8noKZzIXmnAX8gdHX5fLPA3MEVxEN4vbZkQFy8N0LyD9tUqPw== + dependencies: + "@babel/runtime" "^7.27.1" + +"i18next@^23.5.1 || ^24.2.0": + version "24.2.3" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-24.2.3.tgz#3a05f72615cbd7c00d7e348667e2aabef1df753b" + integrity sha512-lfbf80OzkocvX7nmZtu7nSTNbrTYR52sLWxPtlXX1zAhVw8WEnFk4puUkCR4B1dNQwbSpEHHHemcZu//7EcB7A== + dependencies: + "@babel/runtime" "^7.26.10" + +iconv-lite@0.6.3, iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +idb@8.0.3: + version "8.0.3" + resolved "https://registry.npmjs.org/idb/-/idb-8.0.3.tgz" + integrity sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg== + +idb@^7.0.1: + version "7.1.1" + resolved "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz" + integrity sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ== + +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore@^5.2.0: + version "5.3.2" + resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +ignore@^7.0.0, ignore@^7.0.4: + version "7.0.5" + resolved "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz" + integrity sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== + +import-fresh@^3.2.1, import-fresh@^3.3.0: + version "3.3.1" + resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz" + integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-in-the-middle@^1.13.1, import-in-the-middle@^1.8.1: + version "1.13.2" + resolved "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.13.2.tgz" + integrity sha512-Yjp9X7s2eHSXvZYQ0aye6UvwYPrVB5C2k47fuXjFKnYinAByaDZjh4t9MT2wEga9775n6WaIqyHnQhBxYtX2mg== + dependencies: + acorn "^8.14.0" + acorn-import-attributes "^1.9.5" + cjs-module-lexer "^1.2.2" + module-details-from-path "^1.0.3" + +import-local@^3.0.2: + version "3.2.0" + resolved "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ini@^1.3.5: + version "1.3.8" + resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +inline-style-parser@0.2.4: + version "0.2.4" + resolved "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz" + integrity sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q== + +internal-slot@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz" + integrity sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw== + dependencies: + es-errors "^1.3.0" + hasown "^2.0.2" + side-channel "^1.1.0" + +intl-messageformat@^10.1.0: + version "10.7.16" + resolved "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.16.tgz" + integrity sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug== + dependencies: + "@formatjs/ecma402-abstract" "2.3.4" + "@formatjs/fast-memoize" "2.2.7" + "@formatjs/icu-messageformat-parser" "2.11.2" + tslib "^2.8.0" + +is-alphabetical@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz" + integrity sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ== + +is-alphanumerical@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz" + integrity sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw== + dependencies: + is-alphabetical "^2.0.0" + is-decimal "^2.0.0" + +is-array-buffer@^3.0.4, is-array-buffer@^3.0.5: + version "3.0.5" + resolved "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz" + integrity sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + get-intrinsic "^1.2.6" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + +is-async-function@^2.0.0: + version "2.1.1" + resolved "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz" + integrity sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ== + dependencies: + async-function "^1.0.0" + call-bound "^1.0.3" + get-proto "^1.0.1" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" + +is-bigint@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz" + integrity sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ== + dependencies: + has-bigints "^1.0.2" + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-boolean-object@^1.2.1: + version "1.2.2" + resolved "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz" + integrity sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + +is-bun-module@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz" + integrity sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ== + dependencies: + semver "^7.7.1" + +is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + +is-core-module@^2.13.0, is-core-module@^2.15.1: + version "2.16.1" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + dependencies: + hasown "^2.0.2" + +is-data-view@^1.0.1, is-data-view@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz" + integrity sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw== + dependencies: + call-bound "^1.0.2" + get-intrinsic "^1.2.6" + is-typed-array "^1.1.13" + +is-date-object@^1.0.5, is-date-object@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz" + integrity sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg== + dependencies: + call-bound "^1.0.2" + has-tostringtag "^1.0.2" + +is-decimal@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz" + integrity sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-finalizationregistry@^1.1.0: + version "1.1.1" + resolved "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz" + integrity sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg== + dependencies: + call-bound "^1.0.3" + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-generator-function@^1.0.10: + version "1.1.0" + resolved "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz" + integrity sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ== + dependencies: + call-bound "^1.0.3" + get-proto "^1.0.0" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-hexadecimal@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz" + integrity sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg== + +is-map@^2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz" + integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== + +is-module@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz" + integrity sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g== + +is-negated-glob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" + integrity sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug== + +is-number-object@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz" + integrity sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz" + integrity sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg== + +is-path-inside@^3.0.3: + 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@^4.0.0: + version "4.1.0" + resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz" + integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg== + +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + +is-potential-custom-element-name@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz" + integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== + +is-reference@1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz" + integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ== + dependencies: + "@types/estree" "*" + +is-regex@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz" + integrity sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g== + dependencies: + call-bound "^1.0.2" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +is-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz" + integrity sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA== + +is-set@^2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz" + integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== + +is-shared-array-buffer@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz" + integrity sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A== + dependencies: + call-bound "^1.0.3" + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-string@^1.0.7, is-string@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz" + integrity sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + +is-subset@^0.1.1: + version "0.1.1" + resolved "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz" + integrity sha512-6Ybun0IkarhmEqxXCNw/C0bna6Zb/TkfUX9UbwJtK6ObwAVCxmAP308WWTHviM/zAqXk05cdhYsUsZeGQh99iw== + +is-symbol@^1.0.4, is-symbol@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz" + integrity sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w== + dependencies: + call-bound "^1.0.2" + has-symbols "^1.1.0" + safe-regex-test "^1.1.0" + +is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15: + version "1.1.15" + resolved "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz" + integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== + dependencies: + which-typed-array "^1.1.16" + +is-url@^1.2.4: + version "1.2.4" + resolved "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz" + integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== + +is-valid-glob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-1.0.0.tgz#29bf3eff701be2d4d315dbacc39bc39fe8f601aa" + integrity sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA== + +is-weakmap@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz" + integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== + +is-weakref@^1.0.2, is-weakref@^1.1.0: + version "1.1.1" + resolved "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz" + integrity sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew== + dependencies: + call-bound "^1.0.3" + +is-weakset@^2.0.3: + version "2.0.4" + resolved "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz" + integrity sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ== + dependencies: + call-bound "^1.0.3" + get-intrinsic "^1.2.6" + +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + +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.npmjs.org/isexe/-/isexe-2.0.0.tgz" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-instrument@^5.0.4: + version "5.2.1" + resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-instrument@^6.0.0: + version "6.0.3" + resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz" + integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.7" + resolved "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz" + integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +iterator.prototype@^1.1.4: + version "1.1.5" + resolved "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz" + integrity sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g== + dependencies: + define-data-property "^1.1.4" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.6" + get-proto "^1.0.0" + has-symbols "^1.1.0" + set-function-name "^2.0.2" + +jake@^10.8.5: + version "10.9.2" + resolved "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz" + integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA== + dependencies: + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.4" + minimatch "^3.1.2" + +jay-peg@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/jay-peg/-/jay-peg-1.1.1.tgz" + integrity sha512-D62KEuBxz/ip2gQKOEhk/mx14o7eiFRaU+VNNSP4MOiIkwb/D6B3G1Mfas7C/Fit8EsSV2/IWjZElx/Gs6A4ww== + dependencies: + restructure "^3.0.0" + +jest-changed-files@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz" + integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== + dependencies: + execa "^5.0.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + +jest-circus@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz" + integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^1.0.0" + is-generator-fn "^2.0.0" + jest-each "^29.7.0" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + pretty-format "^29.7.0" + pure-rand "^6.0.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz" + integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== + dependencies: + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + chalk "^4.0.0" + create-jest "^29.7.0" + exit "^0.1.2" + import-local "^3.0.2" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + yargs "^17.3.1" + +jest-config@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz" + integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^29.7.0" + jest-environment-node "^29.7.0" + jest-get-type "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-runner "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== + dependencies: + detect-newline "^3.0.0" + +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz" + integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" + +jest-environment-jsdom@29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz" + integrity sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/jsdom" "^20.0.0" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + jsdom "^20.0.0" + +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== + dependencies: + "@jest/types" "^29.6.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz" + integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== + dependencies: + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-util "^29.7.0" + +jest-pnp-resolver@^1.2.2: + version "1.2.3" + resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== + +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz" + integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== + dependencies: + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" + +jest-resolve@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz" + integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-pnp-resolver "^1.2.2" + jest-util "^29.7.0" + jest-validate "^29.7.0" + resolve "^1.20.0" + resolve.exports "^2.0.0" + slash "^3.0.0" + +jest-runner@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz" + integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== + dependencies: + "@jest/console" "^29.7.0" + "@jest/environment" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.13.1" + graceful-fs "^4.2.9" + jest-docblock "^29.7.0" + jest-environment-node "^29.7.0" + jest-haste-map "^29.7.0" + jest-leak-detector "^29.7.0" + jest-message-util "^29.7.0" + jest-resolve "^29.7.0" + jest-runtime "^29.7.0" + jest-util "^29.7.0" + jest-watcher "^29.7.0" + jest-worker "^29.7.0" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz" + integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/globals" "^29.7.0" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.7.0" + graceful-fs "^4.2.9" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + natural-compare "^1.4.0" + pretty-format "^29.7.0" + semver "^7.5.3" + +jest-util@^29.0.0, jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== + dependencies: + "@jest/types" "^29.6.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.6.3" + leven "^3.1.0" + pretty-format "^29.7.0" + +jest-watcher@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz" + integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== + dependencies: + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.13.1" + jest-util "^29.7.0" + string-length "^4.0.1" + +jest-worker@^27.4.5: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz" + integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== + dependencies: + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" + import-local "^3.0.2" + jest-cli "^29.7.0" + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" + 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.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsdom@^20.0.0: + version "20.0.3" + resolved "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz" + integrity sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ== + dependencies: + abab "^2.0.6" + acorn "^8.8.1" + acorn-globals "^7.0.0" + cssom "^0.5.0" + cssstyle "^2.3.0" + data-urls "^3.0.2" + decimal.js "^10.4.2" + domexception "^4.0.0" + escodegen "^2.0.0" + form-data "^4.0.0" + html-encoding-sniffer "^3.0.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.1" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.2" + parse5 "^7.1.1" + saxes "^6.0.0" + symbol-tree "^3.2.4" + tough-cookie "^4.1.2" + w3c-xmlserializer "^4.0.0" + webidl-conversions "^7.0.0" + whatwg-encoding "^2.0.0" + whatwg-mimetype "^3.0.0" + whatwg-url "^11.0.0" + ws "^8.11.0" + xml-name-validator "^4.0.0" + +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + +jsesc@~3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz" + integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz" + 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.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json-schema@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== + dependencies: + minimist "^1.2.0" + +json5@^2.2.0, json5@^2.2.3: + version "2.2.3" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== + optionalDependencies: + graceful-fs "^4.1.6" + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +jsonpointer@^5.0.0: + version "5.0.1" + resolved "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz" + integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ== + +"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.5: + version "3.3.5" + resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz" + integrity sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ== + dependencies: + array-includes "^3.1.6" + array.prototype.flat "^1.3.1" + object.assign "^4.1.4" + object.values "^1.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" + +keyv@^5.3.3: + version "5.3.3" + resolved "https://registry.npmjs.org/keyv/-/keyv-5.3.3.tgz" + integrity sha512-Rwu4+nXI9fqcxiEHtbkvoes2X+QfkTRo1TMkPfwzipGsJlJO/z69vqB4FNl9xJ3xCpAcbkvmEabZfPzrwN3+gQ== + dependencies: + "@keyv/serialize" "^1.0.3" + +kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +known-css-properties@^0.36.0: + version "0.36.0" + resolved "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.36.0.tgz" + integrity sha512-A+9jP+IUmuQsNdsLdcg6Yt7voiMF/D4K83ew0OpJtpu+l34ef7LaohWV0Rc6KNvzw6ZDizkqfyB5JznZnzuKQA== + +language-subtag-registry@^0.3.20: + version "0.3.23" + resolved "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz" + integrity sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ== + +language-tags@^1.0.9: + version "1.0.9" + resolved "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz" + integrity sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA== + dependencies: + language-subtag-registry "^0.3.20" + +lead@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/lead/-/lead-4.0.0.tgz#5317a49effb0e7ec3a0c8fb9c1b24fb716aab939" + integrity sha512-DpMa59o5uGUWWjruMp71e6knmwKU3jRBBn1kjuLWN9EeIOxNeSAwvHf03WIl8g/ZMR2oSQC9ej3yeLBwdDc/pg== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lilconfig@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.3.tgz#a1bcfd6257f9585bf5ae14ceeebb7b559025e4c4" + integrity sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw== + +linebreak@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz" + integrity sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ== + dependencies: + base64-js "0.0.8" + unicode-trie "^2.0.0" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +loader-runner@^4.2.0: + version "4.3.0" + resolved "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz" + integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz" + integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== + +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz" + integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== + +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.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash.sortby@^4.7.0: + version "4.7.0" + resolved "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz" + integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA== + +lodash.truncate@^4.4.2: + version "4.4.2" + resolved "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz" + integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== + +lodash@4.17.21, lodash@^4.17.20, lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +longest-streak@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz" + integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g== + +loose-envify@^1.0.0, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" + 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.npmjs.org/lower-case/-/lower-case-2.0.2.tgz" + integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== + dependencies: + tslib "^2.0.3" + +lru-cache@^10.2.0: + version "10.4.3" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +luxon@3.6.1: + version "3.6.1" + resolved "https://registry.npmjs.org/luxon/-/luxon-3.6.1.tgz" + integrity sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ== + +lz-string@^1.5.0: + version "1.5.0" + resolved "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz" + integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== + +magic-string@0.30.8: + version "0.30.8" + resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz" + integrity sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ== + dependencies: + "@jridgewell/sourcemap-codec" "^1.4.15" + +magic-string@^0.25.0, magic-string@^0.25.7: + version "0.25.9" + resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz" + integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ== + dependencies: + sourcemap-codec "^1.4.8" + +magic-string@^0.30.3: + version "0.30.17" + resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz" + integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + +make-error@^1.1.1, make-error@^1.3.6: + version "1.3.6" + resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +markdown-table@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.4.tgz#fe44d6d410ff9d6f2ea1797a3f60aa4d2b631c2a" + integrity sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw== + +matcher-collection@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/matcher-collection/-/matcher-collection-2.0.1.tgz#90be1a4cf58d6f2949864f65bb3b0f3e41303b29" + integrity sha512-daE62nS2ZQsDg9raM0IlZzLmI2u+7ZapXBwdoeBUKAYERPDDIc0qNqA8E0Rp2D+gspKR7BgIFP52GeujaGXWeQ== + dependencies: + "@types/minimatch" "^3.0.3" + minimatch "^3.0.2" + +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + +mathml-tag-names@^2.1.3: + version "2.1.3" + resolved "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz" + integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== + +mdast-util-find-and-replace@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz#70a3174c894e14df722abf43bc250cbae44b11df" + integrity sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg== + 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.2" + resolved "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz" + integrity sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA== + 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-gfm-autolink-literal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz#abd557630337bd30a6d5a4bd8252e1c2dc0875d5" + integrity sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ== + 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.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz#7778e9d9ca3df7238cc2bd3fa2b1bf6a65b19403" + integrity sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ== + 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.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz#2cdf63b92c2a331406b0fb0db4c077c1b0331751" + integrity sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ== + 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.1" + resolved "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz" + integrity sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ== + 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.2.0" + resolved "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz" + integrity sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q== + 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-stringify-position "^4.0.0" + vfile-message "^4.0.0" + +mdast-util-mdxjs-esm@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz" + 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.1.0" + resolved "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz" + integrity sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w== + dependencies: + "@types/mdast" "^4.0.0" + unist-util-is "^6.0.0" + +mdast-util-to-hast@^13.0.0: + version "13.2.0" + resolved "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz" + integrity sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA== + 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" + vfile "^6.0.0" + +mdast-util-to-markdown@^2.0.0: + version "2.1.2" + resolved "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz" + integrity sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA== + 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-classify-character "^2.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.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz" + integrity sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg== + dependencies: + "@types/mdast" "^4.0.0" + +mdn-data@2.0.28: + version "2.0.28" + resolved "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz" + integrity sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g== + +mdn-data@2.0.30: + version "2.0.30" + resolved "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz" + integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA== + +mdn-data@2.12.2: + version "2.12.2" + resolved "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz" + integrity sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA== + +media-engine@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/media-engine/-/media-engine-1.0.3.tgz" + integrity sha512-aa5tG6sDoK+k70B9iEX1NeyfT8ObCKhNDs6lJVpwF6r8vhUfuKMslIcirq6HIUYuuUYLefcEQOn9bSBOvawtwg== + +"memoize-one@>=3.1.1 <6": + version "5.2.1" + resolved "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz" + integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== + +memoize-one@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz" + integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw== + +meow@^13.2.0: + version "13.2.0" + resolved "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz" + integrity sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA== + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromark-core-commonmark@^2.0.0: + version "2.0.3" + resolved "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz" + integrity sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg== + 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-gfm-autolink-literal@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz#6286aee9686c4462c1e3552a9d505feddceeb935" + integrity sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw== + 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.1.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz#4dab56d4e398b9853f6fe4efac4fc9361f3e0750" + integrity sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw== + 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.1.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz#86106df8b3a692b5f6a92280d3879be6be46d923" + integrity sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw== + 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.1.1" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz#fac70bcbf51fe65f5f44033118d39be8a9b5940b" + integrity sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg== + 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.1.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz#bcc34d805639829990ec175c3eea12bb5b781f2c" + integrity sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw== + 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-factory-destination@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz" + integrity sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA== + 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.1" + resolved "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz" + integrity sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg== + 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-space@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz" + integrity sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-title@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz" + integrity sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw== + 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.1" + resolved "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz" + integrity sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ== + 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@^2.0.0: + version "2.1.1" + resolved "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz" + integrity sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q== + dependencies: + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-chunked@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz" + integrity sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-classify-character@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz" + integrity sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q== + 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.1" + resolved "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz" + integrity sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg== + 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.2" + resolved "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz" + integrity sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-decode-string@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz" + integrity sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ== + 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.1" + resolved "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz" + integrity sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw== + +micromark-util-html-tag-name@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz" + integrity sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA== + +micromark-util-normalize-identifier@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz" + integrity sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-resolve-all@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz" + integrity sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg== + dependencies: + micromark-util-types "^2.0.0" + +micromark-util-sanitize-uri@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz" + integrity sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ== + 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.1.0" + resolved "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz" + integrity sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA== + 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@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz" + integrity sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q== + +micromark-util-types@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz" + integrity sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA== + +micromark@^4.0.0: + version "4.0.2" + resolved "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz" + integrity sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA== + 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.4, micromatch@^4.0.8: + version "4.0.8" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12, mime-types@^2.1.27: + version "2.1.35" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +min-indent@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz" + integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== + +minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^8.0.2: + version "8.0.4" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz" + integrity sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^9.0.0, minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.0, minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +minipass@^4.2.4: + version "4.2.8" + resolved "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz" + integrity sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ== + +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0": + version "7.1.2" + resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + +mktemp@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/mktemp/-/mktemp-0.4.0.tgz#6d0515611c8a8c84e484aa2000129b98e981ff0b" + integrity sha512-IXnMcJ6ZyTuhRmJSjzvHSRhlVPiN9Jwc6e59V0bEJ0ba6OBeX2L0E+mRN1QseeOF4mM+F1Rit6Nh7o+rl2Yn/A== + +module-details-from-path@^1.0.3: + version "1.0.4" + resolved "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz" + integrity sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w== + +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.1, ms@^2.1.3: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +nanoid@^3.3.6, nanoid@^3.3.7, nanoid@^3.3.8: + version "3.3.11" + resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz" + integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== + +napi-postinstall@^0.2.2: + version "0.2.4" + resolved "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.2.4.tgz" + integrity sha512-ZEzHJwBhZ8qQSbknHqYcdtQVr8zUgGyM/q6h6qAyhtyVMNrSgDhrC4disf03dYW0e+czXyLnZINnCTEkWy0eJg== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +next@15.3.3: + version "15.3.3" + resolved "https://registry.npmjs.org/next/-/next-15.3.3.tgz" + integrity sha512-JqNj29hHNmCLtNvd090SyRbXJiivQ+58XjCcrC50Crb5g5u2zi7Y2YivbsEfzk6AtVI80akdOQbaMZwWB1Hthw== + dependencies: + "@next/env" "15.3.3" + "@swc/counter" "0.1.3" + "@swc/helpers" "0.5.15" + busboy "1.6.0" + caniuse-lite "^1.0.30001579" + postcss "8.4.31" + styled-jsx "5.1.6" + optionalDependencies: + "@next/swc-darwin-arm64" "15.3.3" + "@next/swc-darwin-x64" "15.3.3" + "@next/swc-linux-arm64-gnu" "15.3.3" + "@next/swc-linux-arm64-musl" "15.3.3" + "@next/swc-linux-x64-gnu" "15.3.3" + "@next/swc-linux-x64-musl" "15.3.3" + "@next/swc-win32-arm64-msvc" "15.3.3" + "@next/swc-win32-x64-msvc" "15.3.3" + sharp "^0.34.1" + +no-case@^3.0.4: + version "3.0.4" + resolved "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz" + integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== + dependencies: + lower-case "^2.0.2" + tslib "^2.0.3" + +node-ensure@^0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/node-ensure/-/node-ensure-0.0.0.tgz#ecae764150de99861ec5c810fd5d096b183932a7" + integrity sha512-DRI60hzo2oKN1ma0ckc6nQWlHU69RH6xN0sjQTjMpChPfTYvKZdcQFfdYK2RWbJcKyUizSIy/l8OTGxMAM1QDw== + +node-fetch@2.7.0, node-fetch@^2.6.7: + version "2.7.0" + resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.19: + version "2.0.19" + resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz" + integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== + +normalize-path@3.0.0, normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-svg-path@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/normalize-svg-path/-/normalize-svg-path-1.1.0.tgz" + integrity sha512-r9KHKG2UUeB5LoTouwDzBy2VxXlHsiM6fyLQvnJa0S5hrhzqElH/CH7TUGhT1fVvIYBIKf3OpY4YJ4CK+iaqHg== + dependencies: + svg-arc-to-cubic-bezier "^3.0.0" + +now-and-later@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-3.0.0.tgz#cdc045dc5b894b35793cf276cc3206077bb7302d" + integrity sha512-pGO4pzSdaxhWTGkfSfHx3hVzJVslFPwBp2Myq9MYN/ChfJZF87ochMAXnvz6/58RJSf5ik2q9tXprBBrk2cpcg== + dependencies: + once "^1.4.0" + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +nth-check@^2.0.1: + version "2.1.1" + resolved "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== + dependencies: + boolbase "^1.0.0" + +nwsapi@^2.2.2: + version "2.2.20" + resolved "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz" + integrity sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA== + +object-assign@^4.1.0, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-inspect@^1.13.3: + version "1.13.4" + resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.4, object.assign@^4.1.7: + version "4.1.7" + resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz" + integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + has-symbols "^1.1.0" + object-keys "^1.1.1" + +object.entries@^1.1.9: + version "1.1.9" + resolved "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz" + integrity sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.4" + define-properties "^1.2.1" + es-object-atoms "^1.1.1" + +object.fromentries@^2.0.8: + version "2.0.8" + resolved "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz" + integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + +object.groupby@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz" + integrity sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + +object.values@^1.1.6, object.values@^1.2.0, object.values@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz" + integrity sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +once@^1.3.0, once@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + +own-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz" + integrity sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg== + dependencies: + get-intrinsic "^1.2.6" + object-keys "^1.1.1" + safe-push-apply "^1.0.0" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2, p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +pako@^0.2.5: + version "0.2.9" + resolved "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz" + integrity sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA== + +pako@~1.0.5: + version "1.0.11" + resolved "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-entities@^4.0.0: + version "4.0.2" + resolved "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz" + integrity sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw== + dependencies: + "@types/unist" "^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, parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz" + 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-svg-path@^0.1.2: + version "0.1.2" + resolved "https://registry.npmjs.org/parse-svg-path/-/parse-svg-path-0.1.2.tgz" + integrity sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ== + +parse5-htmlparser2-tree-adapter@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz#b5a806548ed893a43e24ccb42fbb78069311e81b" + integrity sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g== + dependencies: + domhandler "^5.0.3" + parse5 "^7.0.0" + +parse5-parser-stream@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz#d7c20eadc37968d272e2c02660fff92dd27e60e1" + integrity sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow== + dependencies: + parse5 "^7.0.0" + +parse5@^7.0.0, parse5@^7.1.1, parse5@^7.3.0: + version "7.3.0" + resolved "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz" + integrity sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw== + dependencies: + entities "^6.0.0" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-posix@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/path-posix/-/path-posix-1.0.0.tgz#06b26113f56beab042545a23bfa88003ccac260f" + integrity sha512-1gJ0WpNIiYcQydgg3Ed8KzvIqTsDpNwq+cjBCssvBtuTWjEqY1AW+i+OepiEMqDCzyro9B2sLAe4RBPajMYFiA== + +path-scurry@^1.6.1: + version "1.11.1" + resolved "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + +path-to-regexp@^2.2.1: + version "2.4.0" + resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.4.0.tgz" + integrity sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +pdf-parse@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pdf-parse/-/pdf-parse-1.1.1.tgz#745e07408679548b3995ff896fd38e96e19d14a7" + integrity sha512-v6ZJ/efsBpGrGGknjtq9J/oC8tZWq0KWL5vQrk2GlzLEQPUDB1ex+13Rmidl1neNN358Jn9EHZw5y07FFtaC7A== + dependencies: + debug "^3.1.0" + node-ensure "^0.0.0" + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz" + integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== + +pg-int8@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz" + integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== + +pg-protocol@*: + version "1.10.0" + resolved "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.0.tgz" + integrity sha512-IpdytjudNuLv8nhlHs/UrVBhU0e78J0oIS/0AVdTbWxSOkFUVdsHC/NrorO6nXsQNDTT1kzDSOMJubBQviX18Q== + +pg-types@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz" + integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== + dependencies: + pg-int8 "1.0.1" + postgres-array "~2.0.0" + postgres-bytea "~1.0.0" + postgres-date "~1.0.4" + postgres-interval "^1.1.0" + +picocolors@^1.0.0, picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +picomatch@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz" + integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== + +pirates@^4.0.4: + version "4.0.7" + resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz" + integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +playwright-core@1.52.0: + version "1.52.0" + resolved "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz" + integrity sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg== + +playwright@1.52.0: + version "1.52.0" + resolved "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz" + integrity sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw== + dependencies: + playwright-core "1.52.0" + optionalDependencies: + fsevents "2.3.2" + +possible-typed-array-names@^1.0.0: + version "1.1.0" + resolved "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz" + integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== + +postcss-resolve-nested-selector@^0.1.6: + version "0.1.6" + resolved "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.6.tgz" + integrity sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw== + +postcss-safe-parser@^7.0.1: + version "7.0.1" + resolved "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz" + integrity sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A== + +postcss-selector-parser@^7.1.0: + version "7.1.0" + resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz" + integrity sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss@8.4.31: + version "8.4.31" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz" + integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== + dependencies: + nanoid "^3.3.6" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +postcss@8.4.49: + version "8.4.49" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz" + integrity sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA== + dependencies: + nanoid "^3.3.7" + picocolors "^1.1.1" + source-map-js "^1.2.1" + +postcss@^8.5.3: + version "8.5.3" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz" + integrity sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A== + dependencies: + nanoid "^3.3.8" + picocolors "^1.1.1" + source-map-js "^1.2.1" + +postgres-array@~2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz" + integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== + +postgres-bytea@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz" + integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w== + +postgres-date@~1.0.4: + version "1.0.7" + resolved "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz" + integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== + +postgres-interval@^1.1.0: + version "1.2.0" + resolved "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz" + integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== + dependencies: + xtend "^4.0.0" + +posthog-js@1.249.3: + version "1.249.3" + resolved "https://registry.npmjs.org/posthog-js/-/posthog-js-1.249.3.tgz" + integrity sha512-+DMUJYADNakPU05tGdJFoWbpyb5gDwRczT2+gFI72Th3315Wl+k7hKwBpzq2zvH5+jKOHjE0SKD/fzf1AFInkw== + dependencies: + core-js "^3.38.1" + fflate "^0.4.8" + preact "^10.19.3" + web-vitals "^4.2.4" + +preact@^10.19.3: + version "10.26.6" + resolved "https://registry.npmjs.org/preact/-/preact-10.26.6.tgz" + integrity sha512-5SRRBinwpwkaD+OqlBDeITlRgvd8I8QlxHJw9AxSdMNV6O+LodN9nUyYGpSF7sadHjs6RzeFShMexC6DbtWr9g== + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@3.5.3: + version "3.5.3" + resolved "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz" + integrity sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw== + +pretty-bytes@^5.3.0, pretty-bytes@^5.4.1: + version "5.6.0" + resolved "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz" + integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== + +pretty-format@^27.0.2: + version "27.5.1" + resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== + dependencies: + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^17.0.1" + +pretty-format@^29.0.0, pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +progress@^2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +promise-map-series@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/promise-map-series/-/promise-map-series-0.3.0.tgz#41873ca3652bb7a042b387d538552da9b576f8a1" + integrity sha512-3npG2NGhTc8BWBolLLf8l/92OxMGaRLbqvIh9wjCHhDXNvk4zsxaTaCpiCunW09qWPrN2zeNSNwRLVBrQQtutA== + +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: + version "15.8.1" + resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + +property-information@^7.0.0: + version "7.1.0" + resolved "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz" + integrity sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ== + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +psl@^1.1.33: + version "1.15.0" + resolved "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz" + integrity sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w== + dependencies: + punycode "^2.3.1" + +punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.1: + version "2.3.1" + resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +pure-rand@^6.0.0: + version "6.1.0" + resolved "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz" + integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== + +querystring@^0.2.0: + version "0.2.1" + resolved "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz" + integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg== + +querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +queue@^6.0.1: + version "6.0.2" + resolved "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz" + integrity sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA== + dependencies: + inherits "~2.0.3" + +quick-temp@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/quick-temp/-/quick-temp-0.1.8.tgz#bab02a242ab8fb0dd758a3c9776b32f9a5d94408" + integrity sha512-YsmIFfD9j2zaFwJkzI6eMG7y0lQP7YeWzgtFgNl38pGWZBSXJooZbOWwkcRot7Vt0Fg9L23pX0tqWU3VvLDsiA== + dependencies: + mktemp "~0.4.0" + rimraf "^2.5.4" + underscore.string "~3.3.4" + +raf@^3.4.1: + version "3.4.1" + resolved "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz" + integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== + dependencies: + performance-now "^2.1.0" + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +react-arborist@3.4.3: + version "3.4.3" + resolved "https://registry.npmjs.org/react-arborist/-/react-arborist-3.4.3.tgz" + integrity sha512-yFnq1nIQhT2uJY4TZVz2tgAiBb9lxSyvF4vC3S8POCK8xLzjGIxVv3/4dmYquQJ7AHxaZZArRGHiHKsEewKdTQ== + dependencies: + react-dnd "^14.0.3" + react-dnd-html5-backend "^14.0.3" + react-window "^1.8.11" + redux "^5.0.0" + use-sync-external-store "^1.2.0" + +react-aria-components@1.5.0: + version "1.5.0" + resolved "https://registry.npmjs.org/react-aria-components/-/react-aria-components-1.5.0.tgz" + integrity sha512-wzf0g6cvWrqAJd4FkisAfFnslx6AJREgOd/NEmVE/RGuDxGTzss4awcwbo98rIVmqbTTFApiygy0SyWGrRZfDA== + dependencies: + "@internationalized/date" "^3.6.0" + "@internationalized/string" "^3.2.5" + "@react-aria/collections" "3.0.0-alpha.6" + "@react-aria/color" "^3.0.2" + "@react-aria/disclosure" "^3.0.0" + "@react-aria/dnd" "^3.8.0" + "@react-aria/focus" "^3.19.0" + "@react-aria/interactions" "^3.22.5" + "@react-aria/live-announcer" "^3.4.1" + "@react-aria/menu" "^3.16.0" + "@react-aria/toolbar" "3.0.0-beta.11" + "@react-aria/tree" "3.0.0-beta.2" + "@react-aria/utils" "^3.26.0" + "@react-aria/virtualizer" "^4.1.0" + "@react-stately/color" "^3.8.1" + "@react-stately/disclosure" "^3.0.0" + "@react-stately/layout" "^4.1.0" + "@react-stately/menu" "^3.9.0" + "@react-stately/selection" "^3.18.0" + "@react-stately/table" "^3.13.0" + "@react-stately/utils" "^3.10.5" + "@react-stately/virtualizer" "^4.2.0" + "@react-types/color" "^3.0.1" + "@react-types/form" "^3.7.8" + "@react-types/grid" "^3.2.10" + "@react-types/shared" "^3.26.0" + "@react-types/table" "^3.10.3" + "@swc/helpers" "^0.5.0" + client-only "^0.0.1" + react-aria "^3.36.0" + react-stately "^3.34.0" + use-sync-external-store "^1.2.0" + +react-aria-components@1.8.0: + version "1.8.0" + resolved "https://registry.npmjs.org/react-aria-components/-/react-aria-components-1.8.0.tgz" + integrity sha512-qNJ/Z4opj1/NKFf1ch/V8rNYar5MXu4J8YVAt2pFgnBRLjVlIlfnENN8Oa5OFiYFCzMPRFdq5mI8RuYIEnvZfg== + dependencies: + "@internationalized/date" "^3.8.0" + "@internationalized/string" "^3.2.6" + "@react-aria/autocomplete" "3.0.0-beta.2" + "@react-aria/collections" "3.0.0-rc.0" + "@react-aria/dnd" "^3.9.2" + "@react-aria/focus" "^3.20.2" + "@react-aria/interactions" "^3.25.0" + "@react-aria/live-announcer" "^3.4.2" + "@react-aria/overlays" "^3.27.0" + "@react-aria/ssr" "^3.9.8" + "@react-aria/toolbar" "3.0.0-beta.15" + "@react-aria/utils" "^3.28.2" + "@react-aria/virtualizer" "^4.1.4" + "@react-stately/autocomplete" "3.0.0-beta.1" + "@react-stately/layout" "^4.2.2" + "@react-stately/selection" "^3.20.1" + "@react-stately/table" "^3.14.1" + "@react-stately/utils" "^3.10.6" + "@react-stately/virtualizer" "^4.3.2" + "@react-types/form" "^3.7.11" + "@react-types/grid" "^3.3.1" + "@react-types/shared" "^3.29.0" + "@react-types/table" "^3.12.0" + "@swc/helpers" "^0.5.0" + client-only "^0.0.1" + react-aria "^3.39.0" + react-stately "^3.37.0" + use-sync-external-store "^1.4.0" + +react-aria-components@1.9.0: + version "1.9.0" + resolved "https://registry.npmjs.org/react-aria-components/-/react-aria-components-1.9.0.tgz" + integrity sha512-7cOYxvODDPn8PlWh7eM6/hcydM9sem9PJfL9XD90hIUGfX/f5wMu4lplpjFVzkoCPe4UcOrqC77k3vpRN+1eaw== + dependencies: + "@internationalized/date" "^3.8.1" + "@internationalized/string" "^3.2.6" + "@react-aria/autocomplete" "3.0.0-beta.3" + "@react-aria/collections" "3.0.0-rc.1" + "@react-aria/dnd" "^3.9.3" + "@react-aria/focus" "^3.20.3" + "@react-aria/interactions" "^3.25.1" + "@react-aria/live-announcer" "^3.4.2" + "@react-aria/overlays" "^3.27.1" + "@react-aria/ssr" "^3.9.8" + "@react-aria/toolbar" "3.0.0-beta.16" + "@react-aria/utils" "^3.29.0" + "@react-aria/virtualizer" "^4.1.5" + "@react-stately/autocomplete" "3.0.0-beta.1" + "@react-stately/layout" "^4.3.0" + "@react-stately/selection" "^3.20.2" + "@react-stately/table" "^3.14.2" + "@react-stately/utils" "^3.10.6" + "@react-stately/virtualizer" "^4.4.0" + "@react-types/form" "^3.7.12" + "@react-types/grid" "^3.3.2" + "@react-types/shared" "^3.29.1" + "@react-types/table" "^3.13.0" + "@swc/helpers" "^0.5.0" + client-only "^0.0.1" + react-aria "^3.40.0" + react-stately "^3.38.0" + use-sync-external-store "^1.4.0" + +react-aria@3.36.0, react-aria@^3.36.0: + version "3.36.0" + resolved "https://registry.npmjs.org/react-aria/-/react-aria-3.36.0.tgz" + integrity sha512-AK5XyIhAN+e5HDlwlF+YwFrOrVI7RYmZ6kg/o7ZprQjkYqYKapXeUpWscmNm/3H2kDboE5Z4ymUnK6ZhobLqOw== + dependencies: + "@internationalized/string" "^3.2.5" + "@react-aria/breadcrumbs" "^3.5.19" + "@react-aria/button" "^3.11.0" + "@react-aria/calendar" "^3.6.0" + "@react-aria/checkbox" "^3.15.0" + "@react-aria/color" "^3.0.2" + "@react-aria/combobox" "^3.11.0" + "@react-aria/datepicker" "^3.12.0" + "@react-aria/dialog" "^3.5.20" + "@react-aria/disclosure" "^3.0.0" + "@react-aria/dnd" "^3.8.0" + "@react-aria/focus" "^3.19.0" + "@react-aria/gridlist" "^3.10.0" + "@react-aria/i18n" "^3.12.4" + "@react-aria/interactions" "^3.22.5" + "@react-aria/label" "^3.7.13" + "@react-aria/link" "^3.7.7" + "@react-aria/listbox" "^3.13.6" + "@react-aria/menu" "^3.16.0" + "@react-aria/meter" "^3.4.18" + "@react-aria/numberfield" "^3.11.9" + "@react-aria/overlays" "^3.24.0" + "@react-aria/progress" "^3.4.18" + "@react-aria/radio" "^3.10.10" + "@react-aria/searchfield" "^3.7.11" + "@react-aria/select" "^3.15.0" + "@react-aria/selection" "^3.21.0" + "@react-aria/separator" "^3.4.4" + "@react-aria/slider" "^3.7.14" + "@react-aria/ssr" "^3.9.7" + "@react-aria/switch" "^3.6.10" + "@react-aria/table" "^3.16.0" + "@react-aria/tabs" "^3.9.8" + "@react-aria/tag" "^3.4.8" + "@react-aria/textfield" "^3.15.0" + "@react-aria/tooltip" "^3.7.10" + "@react-aria/utils" "^3.26.0" + "@react-aria/visually-hidden" "^3.8.18" + "@react-types/shared" "^3.26.0" + +react-aria@3.39.0, react-aria@^3.39.0: + version "3.39.0" + resolved "https://registry.npmjs.org/react-aria/-/react-aria-3.39.0.tgz" + integrity sha512-zXCjR01WnfW4uW0f294uWrvdfwEMHgDFSwMwMBwRafAvmsQea87X5VTAfDmQOAbPa+iQFcngIyH0Pn5CfXNrjw== + dependencies: + "@internationalized/string" "^3.2.6" + "@react-aria/breadcrumbs" "^3.5.23" + "@react-aria/button" "^3.13.0" + "@react-aria/calendar" "^3.8.0" + "@react-aria/checkbox" "^3.15.4" + "@react-aria/color" "^3.0.6" + "@react-aria/combobox" "^3.12.2" + "@react-aria/datepicker" "^3.14.2" + "@react-aria/dialog" "^3.5.24" + "@react-aria/disclosure" "^3.0.4" + "@react-aria/dnd" "^3.9.2" + "@react-aria/focus" "^3.20.2" + "@react-aria/gridlist" "^3.12.0" + "@react-aria/i18n" "^3.12.8" + "@react-aria/interactions" "^3.25.0" + "@react-aria/label" "^3.7.17" + "@react-aria/landmark" "^3.0.2" + "@react-aria/link" "^3.8.0" + "@react-aria/listbox" "^3.14.3" + "@react-aria/menu" "^3.18.2" + "@react-aria/meter" "^3.4.22" + "@react-aria/numberfield" "^3.11.13" + "@react-aria/overlays" "^3.27.0" + "@react-aria/progress" "^3.4.22" + "@react-aria/radio" "^3.11.2" + "@react-aria/searchfield" "^3.8.3" + "@react-aria/select" "^3.15.4" + "@react-aria/selection" "^3.24.0" + "@react-aria/separator" "^3.4.8" + "@react-aria/slider" "^3.7.18" + "@react-aria/ssr" "^3.9.8" + "@react-aria/switch" "^3.7.2" + "@react-aria/table" "^3.17.2" + "@react-aria/tabs" "^3.10.2" + "@react-aria/tag" "^3.5.2" + "@react-aria/textfield" "^3.17.2" + "@react-aria/toast" "^3.0.2" + "@react-aria/tooltip" "^3.8.2" + "@react-aria/tree" "^3.0.2" + "@react-aria/utils" "^3.28.2" + "@react-aria/visually-hidden" "^3.8.22" + "@react-types/shared" "^3.29.0" + +react-aria@^3.40.0: + version "3.40.0" + resolved "https://registry.npmjs.org/react-aria/-/react-aria-3.40.0.tgz" + integrity sha512-pxZusRI1jCBIvJkORJnhAXey/5U/VJa1whCeP6ETzRKepJiXLRPjJerHHJw+3Q6kAJXADL9qds5xdq4nvmyLRA== + dependencies: + "@internationalized/string" "^3.2.6" + "@react-aria/breadcrumbs" "^3.5.24" + "@react-aria/button" "^3.13.1" + "@react-aria/calendar" "^3.8.1" + "@react-aria/checkbox" "^3.15.5" + "@react-aria/color" "^3.0.7" + "@react-aria/combobox" "^3.12.3" + "@react-aria/datepicker" "^3.14.3" + "@react-aria/dialog" "^3.5.25" + "@react-aria/disclosure" "^3.0.5" + "@react-aria/dnd" "^3.9.3" + "@react-aria/focus" "^3.20.3" + "@react-aria/gridlist" "^3.13.0" + "@react-aria/i18n" "^3.12.9" + "@react-aria/interactions" "^3.25.1" + "@react-aria/label" "^3.7.18" + "@react-aria/landmark" "^3.0.3" + "@react-aria/link" "^3.8.1" + "@react-aria/listbox" "^3.14.4" + "@react-aria/menu" "^3.18.3" + "@react-aria/meter" "^3.4.23" + "@react-aria/numberfield" "^3.11.14" + "@react-aria/overlays" "^3.27.1" + "@react-aria/progress" "^3.4.23" + "@react-aria/radio" "^3.11.3" + "@react-aria/searchfield" "^3.8.4" + "@react-aria/select" "^3.15.5" + "@react-aria/selection" "^3.24.1" + "@react-aria/separator" "^3.4.9" + "@react-aria/slider" "^3.7.19" + "@react-aria/ssr" "^3.9.8" + "@react-aria/switch" "^3.7.3" + "@react-aria/table" "^3.17.3" + "@react-aria/tabs" "^3.10.3" + "@react-aria/tag" "^3.6.0" + "@react-aria/textfield" "^3.17.3" + "@react-aria/toast" "^3.0.3" + "@react-aria/tooltip" "^3.8.3" + "@react-aria/tree" "^3.0.3" + "@react-aria/utils" "^3.29.0" + "@react-aria/visually-hidden" "^3.8.23" + "@react-types/shared" "^3.29.1" + +react-dnd-html5-backend@^14.0.3: + version "14.1.0" + resolved "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-14.1.0.tgz" + integrity sha512-6ONeqEC3XKVf4eVmMTe0oPds+c5B9Foyj8p/ZKLb7kL2qh9COYxiBHv3szd6gztqi/efkmriywLUVlPotqoJyw== + dependencies: + dnd-core "14.0.1" + +react-dnd@^14.0.3: + version "14.0.5" + resolved "https://registry.npmjs.org/react-dnd/-/react-dnd-14.0.5.tgz" + integrity sha512-9i1jSgbyVw0ELlEVt/NkCUkxy1hmhJOkePoCH713u75vzHGyXhPDm28oLfc2NMSBjZRM1Y+wRjHXJT3sPrTy+A== + dependencies: + "@react-dnd/invariant" "^2.0.0" + "@react-dnd/shallowequal" "^2.0.0" + dnd-core "14.0.1" + fast-deep-equal "^3.1.3" + hoist-non-react-statics "^3.3.2" + +react-dom@*, react-dom@19.0.0, react-dom@19.1.0: + version "19.1.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.1.0.tgz#133558deca37fa1d682708df8904b25186793623" + integrity sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g== + dependencies: + scheduler "^0.26.0" + +react-i18next@15.5.2: + version "15.5.2" + resolved "https://registry.npmjs.org/react-i18next/-/react-i18next-15.5.2.tgz" + integrity sha512-ePODyXgmZQAOYTbZXQn5rRsSBu3Gszo69jxW6aKmlSgxKAI1fOhDwSu6bT4EKHciWPKQ7v7lPrjeiadR6Gi+1A== + dependencies: + "@babel/runtime" "^7.25.0" + html-parse-stringify "^3.0.1" + +react-intersection-observer@9.16.0: + version "9.16.0" + resolved "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.16.0.tgz" + integrity sha512-w9nJSEp+DrW9KmQmeWHQyfaP6b03v+TdXynaoA964Wxt7mdR3An11z4NNCQgL4gKSK7y1ver2Fq+JKH6CWEzUA== + +react-is@18.2.0, react-is@^18.0.0: + version "18.2.0" + resolved "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== + +react-is@^16.13.1, react-is@^16.7.0: + version "16.13.1" + resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-is@^17.0.1: + version "17.0.2" + resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + +react-lifecycles-compat@^3.0.0: + version "3.0.4" + resolved "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz" + integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== + +react-markdown@10.1.0: + version "10.1.0" + resolved "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz" + integrity sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ== + dependencies: + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + hast-util-to-jsx-runtime "^2.0.0" + html-url-attributes "^3.0.0" + mdast-util-to-hast "^13.0.0" + remark-parse "^11.0.0" + remark-rehype "^11.0.0" + unified "^11.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + +react-modal@3.16.3: + version "3.16.3" + resolved "https://registry.npmjs.org/react-modal/-/react-modal-3.16.3.tgz" + integrity sha512-yCYRJB5YkeQDQlTt17WGAgFJ7jr2QYcWa1SHqZ3PluDmnKJ/7+tVU+E6uKyZ0nODaeEj+xCpK4LcSnKXLMC0Nw== + dependencies: + exenv "^1.2.0" + prop-types "^15.7.2" + react-lifecycles-compat "^3.0.0" + warning "^4.0.3" + +react-remove-scroll-bar@^2.3.7: + version "2.3.8" + resolved "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz" + integrity sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q== + dependencies: + react-style-singleton "^2.2.2" + tslib "^2.0.0" + +react-remove-scroll@^2.6.3: + version "2.6.3" + resolved "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.3.tgz" + integrity sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ== + dependencies: + react-remove-scroll-bar "^2.3.7" + react-style-singleton "^2.2.3" + tslib "^2.1.0" + use-callback-ref "^1.3.3" + use-sidecar "^1.1.3" + +react-resizable-panels@2.1.7: + version "2.1.7" + resolved "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.1.7.tgz" + integrity sha512-JtT6gI+nURzhMYQYsx8DKkx6bSoOGFp7A3CwMrOb8y5jFHFyqwo9m68UhmXRw57fRVJksFn1TSlm3ywEQ9vMgA== + +react-select@5.10.1: + version "5.10.1" + resolved "https://registry.npmjs.org/react-select/-/react-select-5.10.1.tgz" + integrity sha512-roPEZUL4aRZDx6DcsD+ZNreVl+fM8VsKn0Wtex1v4IazH60ILp5xhdlp464IsEAlJdXeD+BhDAFsBVMfvLQueA== + dependencies: + "@babel/runtime" "^7.12.0" + "@emotion/cache" "^11.4.0" + "@emotion/react" "^11.8.1" + "@floating-ui/dom" "^1.0.1" + "@types/react-transition-group" "^4.4.0" + memoize-one "^6.0.0" + prop-types "^15.6.0" + react-transition-group "^4.3.0" + use-isomorphic-layout-effect "^1.2.0" + +react-stately@3.34.0, react-stately@^3.34.0: + version "3.34.0" + resolved "https://registry.npmjs.org/react-stately/-/react-stately-3.34.0.tgz" + integrity sha512-0N9tZ8qQ/CxpJH7ao0O6gr+8955e7VrOskg9N+TIxkFknPetwOCtgppMYhnTfteBV8WfM/vv4OC1NbkgYTqXJA== + dependencies: + "@react-stately/calendar" "^3.6.0" + "@react-stately/checkbox" "^3.6.10" + "@react-stately/collections" "^3.12.0" + "@react-stately/color" "^3.8.1" + "@react-stately/combobox" "^3.10.1" + "@react-stately/data" "^3.12.0" + "@react-stately/datepicker" "^3.11.0" + "@react-stately/disclosure" "^3.0.0" + "@react-stately/dnd" "^3.5.0" + "@react-stately/form" "^3.1.0" + "@react-stately/list" "^3.11.1" + "@react-stately/menu" "^3.9.0" + "@react-stately/numberfield" "^3.9.8" + "@react-stately/overlays" "^3.6.12" + "@react-stately/radio" "^3.10.9" + "@react-stately/searchfield" "^3.5.8" + "@react-stately/select" "^3.6.9" + "@react-stately/selection" "^3.18.0" + "@react-stately/slider" "^3.6.0" + "@react-stately/table" "^3.13.0" + "@react-stately/tabs" "^3.7.0" + "@react-stately/toggle" "^3.8.0" + "@react-stately/tooltip" "^3.5.0" + "@react-stately/tree" "^3.8.6" + "@react-types/shared" "^3.26.0" + +react-stately@3.37.0, react-stately@^3.37.0: + version "3.37.0" + resolved "https://registry.npmjs.org/react-stately/-/react-stately-3.37.0.tgz" + integrity sha512-fm2LRM3XN5lJD48+WQKWvESx54kAIHw0JztCRHMsFmTDgYWX/VASuXKON7LECv227stSEadrxGa8LhPkcelljw== + dependencies: + "@react-stately/calendar" "^3.8.0" + "@react-stately/checkbox" "^3.6.13" + "@react-stately/collections" "^3.12.3" + "@react-stately/color" "^3.8.4" + "@react-stately/combobox" "^3.10.4" + "@react-stately/data" "^3.12.3" + "@react-stately/datepicker" "^3.14.0" + "@react-stately/disclosure" "^3.0.3" + "@react-stately/dnd" "^3.5.3" + "@react-stately/form" "^3.1.3" + "@react-stately/list" "^3.12.1" + "@react-stately/menu" "^3.9.3" + "@react-stately/numberfield" "^3.9.11" + "@react-stately/overlays" "^3.6.15" + "@react-stately/radio" "^3.10.12" + "@react-stately/searchfield" "^3.5.11" + "@react-stately/select" "^3.6.12" + "@react-stately/selection" "^3.20.1" + "@react-stately/slider" "^3.6.3" + "@react-stately/table" "^3.14.1" + "@react-stately/tabs" "^3.8.1" + "@react-stately/toast" "^3.1.0" + "@react-stately/toggle" "^3.8.3" + "@react-stately/tooltip" "^3.5.3" + "@react-stately/tree" "^3.8.9" + "@react-types/shared" "^3.29.0" + +react-stately@^3.38.0: + version "3.38.0" + resolved "https://registry.npmjs.org/react-stately/-/react-stately-3.38.0.tgz" + integrity sha512-zS06DsDhH44z7bsOkMHJ0gnjuLO3UWZ33l7JOgFscrv1qa33IG9fn707sI7GAJdLgDiWXJbeFvXdix2jR1fU1w== + dependencies: + "@react-stately/calendar" "^3.8.1" + "@react-stately/checkbox" "^3.6.14" + "@react-stately/collections" "^3.12.4" + "@react-stately/color" "^3.8.5" + "@react-stately/combobox" "^3.10.5" + "@react-stately/data" "^3.13.0" + "@react-stately/datepicker" "^3.14.1" + "@react-stately/disclosure" "^3.0.4" + "@react-stately/dnd" "^3.5.4" + "@react-stately/form" "^3.1.4" + "@react-stately/list" "^3.12.2" + "@react-stately/menu" "^3.9.4" + "@react-stately/numberfield" "^3.9.12" + "@react-stately/overlays" "^3.6.16" + "@react-stately/radio" "^3.10.13" + "@react-stately/searchfield" "^3.5.12" + "@react-stately/select" "^3.6.13" + "@react-stately/selection" "^3.20.2" + "@react-stately/slider" "^3.6.4" + "@react-stately/table" "^3.14.2" + "@react-stately/tabs" "^3.8.2" + "@react-stately/toast" "^3.1.0" + "@react-stately/toggle" "^3.8.4" + "@react-stately/tooltip" "^3.5.4" + "@react-stately/tree" "^3.8.10" + "@react-types/shared" "^3.29.1" + +react-style-singleton@^2.2.2, react-style-singleton@^2.2.3: + version "2.2.3" + resolved "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz" + integrity sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ== + dependencies: + get-nonce "^1.0.0" + tslib "^2.0.0" + +react-transition-group@^4.3.0: + version "4.4.5" + resolved "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz" + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + +react-window@^1.8.11: + version "1.8.11" + resolved "https://registry.npmjs.org/react-window/-/react-window-1.8.11.tgz" + integrity sha512-+SRbUVT2scadgFSWx+R1P754xHPEqvcfSfVX10QYg6POOz+WNgkN48pS+BtZNIMGiL1HYrSEiCkwsMS15QogEQ== + dependencies: + "@babel/runtime" "^7.0.0" + memoize-one ">=3.1.1 <6" + +react@*, react@19.0.0, react@19.1.0: + version "19.1.0" + resolved "https://registry.yarnpkg.com/react/-/react-19.1.0.tgz#926864b6c48da7627f004795d6cce50e90793b75" + integrity sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg== + +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" + +readable-stream@~2.3.6: + 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" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +redent@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz" + integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== + dependencies: + indent-string "^4.0.0" + strip-indent "^3.0.0" + +redux@^4.1.1: + version "4.2.1" + resolved "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz" + integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w== + dependencies: + "@babel/runtime" "^7.9.2" + +redux@^5.0.0: + version "5.0.1" + resolved "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz" + integrity sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w== + +reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9: + version "1.0.10" + resolved "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz" + integrity sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.9" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.7" + get-proto "^1.0.1" + which-builtin-type "^1.2.1" + +regenerate-unicode-properties@^10.2.0: + version "10.2.0" + resolved "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz" + integrity sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA== + dependencies: + regenerate "^1.4.2" + +regenerate@^1.4.2: + version "1.4.2" + resolved "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz" + integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== + +regexp.prototype.flags@^1.5.3: + version "1.5.4" + resolved "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz" + integrity sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-errors "^1.3.0" + get-proto "^1.0.1" + gopd "^1.2.0" + set-function-name "^2.0.2" + +regexpu-core@^6.2.0: + version "6.2.0" + resolved "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz" + integrity sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA== + dependencies: + regenerate "^1.4.2" + regenerate-unicode-properties "^10.2.0" + regjsgen "^0.8.0" + regjsparser "^0.12.0" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.1.0" + +regjsgen@^0.8.0: + version "0.8.0" + resolved "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz" + integrity sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q== + +regjsparser@^0.12.0: + version "0.12.0" + resolved "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz" + integrity sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ== + dependencies: + jsesc "~3.0.2" + +remark-gfm@4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/remark-gfm/-/remark-gfm-4.0.1.tgz#33227b2a74397670d357bf05c098eaf8513f0d6b" + integrity sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg== + 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-parse@^11.0.0: + version "11.0.0" + resolved "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz" + 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.1.2" + resolved "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz" + integrity sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw== + 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" + +remove-trailing-separator@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw== + +replace-ext@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-2.0.0.tgz#9471c213d22e1bcc26717cd6e50881d88f812b06" + integrity sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +require-in-the-middle@^7.1.1: + version "7.5.2" + resolved "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.5.2.tgz" + integrity sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ== + dependencies: + debug "^4.3.5" + module-details-from-path "^1.0.3" + resolve "^1.22.8" + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve-options@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-options/-/resolve-options-2.0.0.tgz#a1a57a9949db549dd075de3f5550675f02f1e4c5" + integrity sha512-/FopbmmFOQCfsCx77BRFdKOniglTiHumLgwvd6IDPihy1GKkadZbgQJBcTb2lMzSR1pndzd96b1nZrreZ7+9/A== + dependencies: + value-or-function "^4.0.0" + +resolve-pkg-maps@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz" + integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== + +resolve.exports@^2.0.0: + version "2.0.3" + resolved "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz" + integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== + +resolve@1.22.8, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.1, resolve@^1.22.4, resolve@^1.22.8: + version "1.22.8" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +resolve@^2.0.0-next.5: + version "2.0.0-next.5" + resolved "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz" + integrity sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +restructure@^3.0.0: + version "3.0.2" + resolved "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz" + integrity sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw== + +reusify@^1.0.4: + version "1.1.0" + resolved "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz" + integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== + +rgbcolor@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz" + integrity sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw== + +rimraf@^2.5.4: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + +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" + +rollup@4.35.0: + version "4.35.0" + resolved "https://registry.npmjs.org/rollup/-/rollup-4.35.0.tgz" + integrity sha512-kg6oI4g+vc41vePJyO6dHt/yl0Rz3Thv0kJeVQ3D1kS3E5XSuKbPc29G4IpT/Kv1KQwgHVcN+HtyS+HYLNSvQg== + dependencies: + "@types/estree" "1.0.6" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.35.0" + "@rollup/rollup-android-arm64" "4.35.0" + "@rollup/rollup-darwin-arm64" "4.35.0" + "@rollup/rollup-darwin-x64" "4.35.0" + "@rollup/rollup-freebsd-arm64" "4.35.0" + "@rollup/rollup-freebsd-x64" "4.35.0" + "@rollup/rollup-linux-arm-gnueabihf" "4.35.0" + "@rollup/rollup-linux-arm-musleabihf" "4.35.0" + "@rollup/rollup-linux-arm64-gnu" "4.35.0" + "@rollup/rollup-linux-arm64-musl" "4.35.0" + "@rollup/rollup-linux-loongarch64-gnu" "4.35.0" + "@rollup/rollup-linux-powerpc64le-gnu" "4.35.0" + "@rollup/rollup-linux-riscv64-gnu" "4.35.0" + "@rollup/rollup-linux-s390x-gnu" "4.35.0" + "@rollup/rollup-linux-x64-gnu" "4.35.0" + "@rollup/rollup-linux-x64-musl" "4.35.0" + "@rollup/rollup-win32-arm64-msvc" "4.35.0" + "@rollup/rollup-win32-ia32-msvc" "4.35.0" + "@rollup/rollup-win32-x64-msvc" "4.35.0" + fsevents "~2.3.2" + +rollup@^2.43.1: + version "2.79.2" + resolved "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz" + integrity sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ== + optionalDependencies: + fsevents "~2.3.2" + +rsvp@^4.8.5: + version "4.8.5" + resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" + integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== + +rsvp@~3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.2.1.tgz#07cb4a5df25add9e826ebc67dcc9fd89db27d84a" + integrity sha512-Rf4YVNYpKjZ6ASAmibcwTNciQ5Co5Ztq6iZPEykHpkoflnD/K5ryE/rHehFsTm4NJj8nKDhbi3eKBWGogmNnkg== + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +safe-array-concat@^1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz" + integrity sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + get-intrinsic "^1.2.6" + has-symbols "^1.1.0" + isarray "^2.0.5" + +safe-buffer@^5.1.0, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-push-apply@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz" + integrity sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA== + dependencies: + es-errors "^1.3.0" + isarray "^2.0.5" + +safe-regex-test@^1.0.3, safe-regex-test@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz" + integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + is-regex "^1.2.1" + +"safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +saxes@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz" + integrity sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA== + dependencies: + xmlchars "^2.2.0" + +scheduler@0.25.0-rc-603e6108-20241029: + version "0.25.0-rc-603e6108-20241029" + resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0-rc-603e6108-20241029.tgz" + integrity sha512-pFwF6H1XrSdYYNLfOcGlM28/j8CGLu8IvdrxqhjWULe2bPcKiKW4CV+OWqR/9fT52mywx65l7ysNkjLKBda7eA== + +scheduler@^0.26.0: + version "0.26.0" + resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz" + integrity sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA== + +schema-utils@^4.3.0, schema-utils@^4.3.2: + version "4.3.2" + resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz" + integrity sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ== + dependencies: + "@types/json-schema" "^7.0.9" + ajv "^8.9.0" + ajv-formats "^2.1.1" + ajv-keywords "^5.1.0" + +secure-json-parse@^2.7.0: + version "2.7.0" + resolved "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz" + integrity sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw== + +semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.5.2, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.7.1, semver@^7.7.2: + version "7.7.2" + resolved "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz" + integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== + +serialize-javascript@^6.0.1, serialize-javascript@^6.0.2: + version "6.0.2" + resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz" + integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== + dependencies: + randombytes "^2.1.0" + +set-function-length@^1.2.2: + version "1.2.2" + resolved "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +set-function-name@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz" + integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.2" + +set-proto@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz" + integrity sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw== + dependencies: + dunder-proto "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + +shallowequal@1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz" + integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== + +sharp@^0.34.1: + version "0.34.1" + resolved "https://registry.npmjs.org/sharp/-/sharp-0.34.1.tgz" + integrity sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg== + dependencies: + color "^4.2.3" + detect-libc "^2.0.3" + semver "^7.7.1" + optionalDependencies: + "@img/sharp-darwin-arm64" "0.34.1" + "@img/sharp-darwin-x64" "0.34.1" + "@img/sharp-libvips-darwin-arm64" "1.1.0" + "@img/sharp-libvips-darwin-x64" "1.1.0" + "@img/sharp-libvips-linux-arm" "1.1.0" + "@img/sharp-libvips-linux-arm64" "1.1.0" + "@img/sharp-libvips-linux-ppc64" "1.1.0" + "@img/sharp-libvips-linux-s390x" "1.1.0" + "@img/sharp-libvips-linux-x64" "1.1.0" + "@img/sharp-libvips-linuxmusl-arm64" "1.1.0" + "@img/sharp-libvips-linuxmusl-x64" "1.1.0" + "@img/sharp-linux-arm" "0.34.1" + "@img/sharp-linux-arm64" "0.34.1" + "@img/sharp-linux-s390x" "0.34.1" + "@img/sharp-linux-x64" "0.34.1" + "@img/sharp-linuxmusl-arm64" "0.34.1" + "@img/sharp-linuxmusl-x64" "0.34.1" + "@img/sharp-wasm32" "0.34.1" + "@img/sharp-win32-ia32" "0.34.1" + "@img/sharp-win32-x64" "0.34.1" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shimmer@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz" + integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== + +side-channel-list@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz" + integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + +side-channel@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz" + integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + side-channel-list "^1.0.0" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" + +signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz" + integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== + dependencies: + is-arrayish "^0.3.1" + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +smob@^1.0.0: + version "1.5.0" + resolved "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz" + integrity sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig== + +snake-case@^3.0.4: + version "3.0.4" + resolved "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz" + integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg== + dependencies: + dot-case "^3.0.4" + tslib "^2.0.3" + +sort-keys@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-5.1.0.tgz#50a3f3d1ad3c5a76d043e0aeeba7299241e9aa5c" + integrity sha512-aSbHV0DaBcr7u0PVHXzM6NbZNAtrr9sF6+Qfs9UUVG7Ll3jQ6hHi8F/xqIIcn2rvIVbr0v/2zyjSdwSV47AgLQ== + dependencies: + is-plain-obj "^4.0.0" + +source-list-map@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz" + integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== + +source-map-js@^1.0.1, source-map-js@^1.0.2, source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.5.7: + version "0.5.7" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz" + integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.8.0-beta.0: + version "0.8.0-beta.0" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz" + integrity sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA== + dependencies: + whatwg-url "^7.0.0" + +sourcemap-codec@^1.4.8: + version "1.4.8" + resolved "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + +space-separated-tokens@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz" + integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q== + +sprintf-js@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" + integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stable-hash@^0.0.5: + version "0.0.5" + resolved "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz" + integrity sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA== + +stack-utils@^2.0.3: + version "2.0.6" + resolved "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +stackblur-canvas@^2.0.0: + version "2.7.0" + resolved "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz" + integrity sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ== + +stacktrace-parser@^0.1.10: + version "0.1.11" + resolved "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.11.tgz" + integrity sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg== + dependencies: + type-fest "^0.7.1" + +stream-composer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/stream-composer/-/stream-composer-1.0.2.tgz#7ee61ca1587bf5f31b2e29aa2093cbf11442d152" + integrity sha512-bnBselmwfX5K10AH6L4c8+S5lgZMWI7ZYrz2rvYjCPB2DIMC4Ig8OpxGpNJSxRZ58oti7y1IcNvjBAz9vW5m4w== + dependencies: + streamx "^2.13.2" + +streamsearch@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz" + integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== + +streamx@^2.12.0, streamx@^2.12.5, streamx@^2.13.2, streamx@^2.14.0: + version "2.22.1" + resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.22.1.tgz#c97cbb0ce18da4f4db5a971dc9ab68ff5dc7f5a5" + integrity sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA== + dependencies: + fast-fifo "^1.3.2" + text-decoder "^1.1.0" + optionalDependencies: + bare-events "^2.2.0" + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + 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@^7.0.0, string-width@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.2.0.tgz#b5bb8e2165ce275d4d43476dd2700ad9091db6dc" + integrity sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ== + dependencies: + emoji-regex "^10.3.0" + get-east-asian-width "^1.0.0" + strip-ansi "^7.1.0" + +string.prototype.includes@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz" + integrity sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.3" + +string.prototype.matchall@^4.0.12, string.prototype.matchall@^4.0.6: + version "4.0.12" + resolved "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz" + integrity sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + es-abstract "^1.23.6" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.6" + gopd "^1.2.0" + has-symbols "^1.1.0" + internal-slot "^1.1.0" + regexp.prototype.flags "^1.5.3" + set-function-name "^2.0.2" + side-channel "^1.1.0" + +string.prototype.repeat@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz" + integrity sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + +string.prototype.trim@^1.2.10: + version "1.2.10" + resolved "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz" + integrity sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + define-data-property "^1.1.4" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-object-atoms "^1.0.0" + has-property-descriptors "^1.0.2" + +string.prototype.trimend@^1.0.8, string.prototype.trimend@^1.0.9: + version "1.0.9" + resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz" + integrity sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +string.prototype.trimstart@^1.0.8: + version "1.0.8" + resolved "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz" + integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +string_decoder@^1.1.1, string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +stringify-entities@^4.0.0: + version "4.0.4" + resolved "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz" + integrity sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg== + 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.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz" + 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.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.1.0: + 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@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-comments@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz" + integrity sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +style-to-js@^1.0.0: + version "1.1.17" + resolved "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.17.tgz" + integrity sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA== + dependencies: + style-to-object "1.0.9" + +style-to-object@1.0.9: + version "1.0.9" + resolved "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.9.tgz" + integrity sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw== + dependencies: + inline-style-parser "0.2.4" + +styled-components@6.1.18: + version "6.1.18" + resolved "https://registry.npmjs.org/styled-components/-/styled-components-6.1.18.tgz" + integrity sha512-Mvf3gJFzZCkhjY2Y/Fx9z1m3dxbza0uI9H1CbNZm/jSHCojzJhQ0R7bByrlFJINnMzz/gPulpoFFGymNwrsMcw== + dependencies: + "@emotion/is-prop-valid" "1.2.2" + "@emotion/unitless" "0.8.1" + "@types/stylis" "4.2.5" + css-to-react-native "3.2.0" + csstype "3.1.3" + postcss "8.4.49" + shallowequal "1.1.0" + stylis "4.3.2" + tslib "2.6.2" + +styled-jsx@5.1.6: + version "5.1.6" + resolved "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz" + integrity sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA== + dependencies: + client-only "0.0.1" + +stylelint-config-recommended@^16.0.0: + version "16.0.0" + resolved "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-16.0.0.tgz" + integrity sha512-4RSmPjQegF34wNcK1e1O3Uz91HN8P1aFdFzio90wNK9mjgAI19u5vsU868cVZboKzCaa5XbpvtTzAAGQAxpcXA== + +stylelint-config-standard@38.0.0: + version "38.0.0" + resolved "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-38.0.0.tgz" + integrity sha512-uj3JIX+dpFseqd/DJx8Gy3PcRAJhlEZ2IrlFOc4LUxBX/PNMEQ198x7LCOE2Q5oT9Vw8nyc4CIL78xSqPr6iag== + dependencies: + stylelint-config-recommended "^16.0.0" + +stylelint-prettier@5.0.3: + version "5.0.3" + resolved "https://registry.npmjs.org/stylelint-prettier/-/stylelint-prettier-5.0.3.tgz" + integrity sha512-B6V0oa35ekRrKZlf+6+jA+i50C4GXJ7X1PPmoCqSUoXN6BrNF6NhqqhanvkLjqw2qgvrS0wjdpeC+Tn06KN3jw== + dependencies: + prettier-linter-helpers "^1.0.0" + +stylelint@16.20.0: + version "16.20.0" + resolved "https://registry.npmjs.org/stylelint/-/stylelint-16.20.0.tgz" + integrity sha512-B5Myu9WRxrgKuLs3YyUXLP2H0mrbejwNxPmyADlACWwFsrL8Bmor/nTSh4OMae5sHjOz6gkSeccQH34gM4/nAw== + dependencies: + "@csstools/css-parser-algorithms" "^3.0.4" + "@csstools/css-tokenizer" "^3.0.3" + "@csstools/media-query-list-parser" "^4.0.2" + "@csstools/selector-specificity" "^5.0.0" + "@dual-bundle/import-meta-resolve" "^4.1.0" + balanced-match "^2.0.0" + colord "^2.9.3" + cosmiconfig "^9.0.0" + css-functions-list "^3.2.3" + css-tree "^3.1.0" + debug "^4.4.1" + fast-glob "^3.3.3" + fastest-levenshtein "^1.0.16" + file-entry-cache "^10.1.0" + global-modules "^2.0.0" + globby "^11.1.0" + globjoin "^0.1.4" + html-tags "^3.3.1" + ignore "^7.0.4" + imurmurhash "^0.1.4" + is-plain-object "^5.0.0" + known-css-properties "^0.36.0" + mathml-tag-names "^2.1.3" + meow "^13.2.0" + micromatch "^4.0.8" + normalize-path "^3.0.0" + picocolors "^1.1.1" + postcss "^8.5.3" + postcss-resolve-nested-selector "^0.1.6" + postcss-safe-parser "^7.0.1" + postcss-selector-parser "^7.1.0" + postcss-value-parser "^4.2.0" + resolve-from "^5.0.0" + string-width "^4.2.3" + supports-hyperlinks "^3.2.0" + svg-tags "^1.0.0" + table "^6.9.0" + write-file-atomic "^5.0.1" + +stylis@4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz" + integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== + +stylis@4.3.2: + version "4.3.2" + resolved "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz" + integrity sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg== + +supports-color@^7.0.0, supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" + 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.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-hyperlinks@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz" + integrity sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +svg-arc-to-cubic-bezier@^3.0.0, svg-arc-to-cubic-bezier@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz" + integrity sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g== + +svg-parser@^2.0.4: + version "2.0.4" + resolved "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz" + integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== + +svg-pathdata@^6.0.3: + version "6.0.3" + resolved "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz" + integrity sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw== + +svg-tags@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz" + integrity sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA== + +svgo@^3.0.2: + version "3.3.2" + resolved "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz" + integrity sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw== + dependencies: + "@trysound/sax" "0.2.0" + commander "^7.2.0" + css-select "^5.1.0" + css-tree "^2.3.1" + css-what "^6.1.0" + csso "^5.0.5" + picocolors "^1.0.0" + +swr@^2.2.5: + version "2.3.3" + resolved "https://registry.npmjs.org/swr/-/swr-2.3.3.tgz" + integrity sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A== + dependencies: + dequal "^2.0.3" + use-sync-external-store "^1.4.0" + +symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + +symlink-or-copy@^1.1.8, symlink-or-copy@^1.2.0, symlink-or-copy@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/symlink-or-copy/-/symlink-or-copy-1.3.1.tgz#9506dd64d8e98fa21dcbf4018d1eab23e77f71fe" + integrity sha512-0K91MEXFpBUaywiwSSkmKjnGcasG/rVBXFLJz5DrgGabpYD6N+3yZrfD6uUIfpuTu65DZLHi7N8CizHc07BPZA== + +synckit@^0.11.7: + version "0.11.8" + resolved "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz" + integrity sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A== + dependencies: + "@pkgr/core" "^0.2.4" + +table@^6.9.0: + version "6.9.0" + resolved "https://registry.npmjs.org/table/-/table-6.9.0.tgz" + integrity sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A== + dependencies: + ajv "^8.0.1" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + +tapable@^2.1.1, tapable@^2.2.0: + version "2.2.1" + resolved "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + +teex@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/teex/-/teex-1.0.1.tgz#b8fa7245ef8e8effa8078281946c85ab780a0b12" + integrity sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg== + dependencies: + streamx "^2.12.5" + +temp-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz" + integrity sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg== + +tempy@^0.6.0: + version "0.6.0" + resolved "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz" + integrity sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw== + dependencies: + is-stream "^2.0.0" + temp-dir "^2.0.0" + type-fest "^0.16.0" + unique-string "^2.0.0" + +terser-webpack-plugin@^5.3.11: + version "5.3.14" + resolved "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz" + integrity sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.25" + jest-worker "^27.4.5" + schema-utils "^4.3.0" + serialize-javascript "^6.0.2" + terser "^5.31.1" + +terser@^5.17.4, terser@^5.31.1: + version "5.39.1" + resolved "https://registry.npmjs.org/terser/-/terser-5.39.1.tgz" + integrity sha512-Mm6+uad0ZuDtcV8/4uOZQDQ8RuiC5Pu+iZRedJtF7yA/27sPL7d++In/AJKpWZlU3SYMPPkVfwetn6sgZ66pUA== + dependencies: + "@jridgewell/source-map" "^0.3.3" + acorn "^8.8.2" + commander "^2.20.0" + source-map-support "~0.5.20" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-decoder@^1.1.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/text-decoder/-/text-decoder-1.2.3.tgz#b19da364d981b2326d5f43099c310cc80d770c65" + integrity sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA== + dependencies: + b4a "^1.6.4" + +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== + +throttleit@2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz" + integrity sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw== + +through2@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + +tiny-inflate@^1.0.0, tiny-inflate@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz" + integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw== + +tinyglobby@^0.2.13: + version "0.2.13" + resolved "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz" + integrity sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw== + dependencies: + fdir "^6.4.4" + picomatch "^4.0.2" + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +to-through@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/to-through/-/to-through-3.0.0.tgz#bf4956eaca5a0476474850a53672bed6906ace54" + integrity sha512-y8MN937s/HVhEoBU1SxfHC+wxCHkV1a9gW8eAdTadYh/bGyesZIVcbjI+mSpFbSVwQici/XjBjuUyri1dnXwBw== + dependencies: + streamx "^2.12.5" + +tough-cookie@^4.1.2: + version "4.1.4" + resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz" + integrity sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.2.0" + url-parse "^1.5.3" + +tr46@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz" + integrity sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA== + dependencies: + punycode "^2.1.0" + +tr46@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz" + integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA== + dependencies: + punycode "^2.1.1" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +trim-lines@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz" + integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg== + +trough@^2.0.0: + version "2.2.0" + resolved "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz" + integrity sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw== + +ts-api-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz" + integrity sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ== + +ts-jest@29.3.4: + version "29.3.4" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.3.4.tgz#9354472aceae1d3867a80e8e02014ea5901aee41" + integrity sha512-Iqbrm8IXOmV+ggWHOTEbjwyCf2xZlUMv5npExksXohL+tk8va4Fjhb+X2+Rt9NBmgO7bJ8WpnMLOwih/DnMlFA== + dependencies: + bs-logger "^0.2.6" + ejs "^3.1.10" + fast-json-stable-stringify "^2.1.0" + jest-util "^29.0.0" + json5 "^2.2.3" + lodash.memoize "^4.1.2" + make-error "^1.3.6" + semver "^7.7.2" + type-fest "^4.41.0" + yargs-parser "^21.1.1" + +ts-node@10.9.2: + version "10.9.2" + resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +tsconfig-paths@^3.15.0: + version "3.15.0" + resolved "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz" + integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tslib@2.6.2: + version "2.6.2" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + +tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.6.2, tslib@^2.8.0: + version "2.8.1" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.16.0: + version "0.16.0" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz" + integrity sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-fest@^0.7.1: + version "0.7.1" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz" + integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg== + +type-fest@^4.41.0: + version "4.41.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58" + integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== + +typed-array-buffer@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz" + integrity sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-typed-array "^1.1.14" + +typed-array-byte-length@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz" + integrity sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg== + dependencies: + call-bind "^1.0.8" + for-each "^0.3.3" + gopd "^1.2.0" + has-proto "^1.2.0" + is-typed-array "^1.1.14" + +typed-array-byte-offset@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz" + integrity sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + for-each "^0.3.3" + gopd "^1.2.0" + has-proto "^1.2.0" + is-typed-array "^1.1.15" + reflect.getprototypeof "^1.0.9" + +typed-array-length@^1.0.7: + version "1.0.7" + resolved "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz" + integrity sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + is-typed-array "^1.1.13" + possible-typed-array-names "^1.0.0" + reflect.getprototypeof "^1.0.6" + +typescript@*, typescript@5.8.3, typescript@^5.0.4: + version "5.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" + integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== + +unbox-primitive@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz" + integrity sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw== + dependencies: + call-bound "^1.0.3" + has-bigints "^1.0.2" + has-symbols "^1.1.0" + which-boxed-primitive "^1.1.1" + +underscore.string@~3.3.4: + version "3.3.6" + resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-3.3.6.tgz#ad8cf23d7423cb3b53b898476117588f4e2f9159" + integrity sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ== + dependencies: + sprintf-js "^1.1.1" + util-deprecate "^1.0.2" + +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + +undici@^7.10.0: + version "7.11.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-7.11.0.tgz#8e13a54f62afa756666c0590c38b3866e286d0b3" + integrity sha512-heTSIac3iLhsmZhUCjyS3JQEkZELateufzZuBaVM5RHXdSBMb1LPMQf5x+FH7qjsZYDP0ttAc3nnVpUB+wYbOg== + +unicode-canonical-property-names-ecmascript@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz" + integrity sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg== + +unicode-match-property-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz" + 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.2.0" + resolved "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz" + integrity sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg== + +unicode-properties@^1.4.0, unicode-properties@^1.4.1: + version "1.4.1" + resolved "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz" + integrity sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg== + dependencies: + base64-js "^1.3.0" + unicode-trie "^2.0.0" + +unicode-property-aliases-ecmascript@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz" + integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== + +unicode-trie@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz" + integrity sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ== + dependencies: + pako "^0.2.5" + tiny-inflate "^1.0.0" + +unified@^11.0.0: + version "11.0.5" + resolved "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz" + integrity sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA== + 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@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz" + integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== + dependencies: + crypto-random-string "^2.0.0" + +unist-util-is@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz" + integrity sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-position@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz" + integrity sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-stringify-position@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz" + 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.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz" + 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.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz" + 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@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +universalify@^0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz" + integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== + +universalify@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + +unplugin@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/unplugin/-/unplugin-1.0.1.tgz" + integrity sha512-aqrHaVBWW1JVKBHmGo33T5TxeL0qWzfvjWokObHA9bYmN7eNDkwOxmLjhioHl9878qDFMAaT51XNroRyuz7WxA== + dependencies: + acorn "^8.8.1" + chokidar "^3.5.3" + webpack-sources "^3.2.3" + webpack-virtual-modules "^0.5.0" + +unrs-resolver@^1.6.2: + version "1.7.2" + resolved "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.7.2.tgz" + integrity sha512-BBKpaylOW8KbHsu378Zky/dGh4ckT/4NW/0SHRABdqRLcQJ2dAOjDo9g97p04sWflm0kqPqpUatxReNV/dqI5A== + dependencies: + napi-postinstall "^0.2.2" + optionalDependencies: + "@unrs/resolver-binding-darwin-arm64" "1.7.2" + "@unrs/resolver-binding-darwin-x64" "1.7.2" + "@unrs/resolver-binding-freebsd-x64" "1.7.2" + "@unrs/resolver-binding-linux-arm-gnueabihf" "1.7.2" + "@unrs/resolver-binding-linux-arm-musleabihf" "1.7.2" + "@unrs/resolver-binding-linux-arm64-gnu" "1.7.2" + "@unrs/resolver-binding-linux-arm64-musl" "1.7.2" + "@unrs/resolver-binding-linux-ppc64-gnu" "1.7.2" + "@unrs/resolver-binding-linux-riscv64-gnu" "1.7.2" + "@unrs/resolver-binding-linux-riscv64-musl" "1.7.2" + "@unrs/resolver-binding-linux-s390x-gnu" "1.7.2" + "@unrs/resolver-binding-linux-x64-gnu" "1.7.2" + "@unrs/resolver-binding-linux-x64-musl" "1.7.2" + "@unrs/resolver-binding-wasm32-wasi" "1.7.2" + "@unrs/resolver-binding-win32-arm64-msvc" "1.7.2" + "@unrs/resolver-binding-win32-ia32-msvc" "1.7.2" + "@unrs/resolver-binding-win32-x64-msvc" "1.7.2" + +upath@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz" + integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== + +update-browserslist-db@^1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz" + integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +url-parse@^1.5.3: + version "1.5.10" + resolved "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + +use-callback-ref@^1.3.3: + version "1.3.3" + resolved "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz" + integrity sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg== + dependencies: + tslib "^2.0.0" + +use-debounce@10.0.4: + version "10.0.4" + resolved "https://registry.npmjs.org/use-debounce/-/use-debounce-10.0.4.tgz" + integrity sha512-6Cf7Yr7Wk7Kdv77nnJMf6de4HuDE4dTxKij+RqE9rufDsI6zsbjyAxcH5y2ueJCQAnfgKbzXbZHYlkFwmBlWkw== + +use-isomorphic-layout-effect@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.0.tgz" + integrity sha512-q6ayo8DWoPZT0VdG4u3D3uxcgONP3Mevx2i2b0434cwWBoL+aelL1DzkXI6w3PhTZzUeR2kaVlZn70iCiseP6w== + +use-sidecar@^1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz" + integrity sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ== + dependencies: + detect-node-es "^1.1.0" + tslib "^2.0.0" + +use-sync-external-store@^1.2.0, use-sync-external-store@^1.2.2, use-sync-external-store@^1.4.0: + version "1.5.0" + resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz" + integrity sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A== + +util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +uuid@^9.0.0: + version "9.0.1" + resolved "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +v8-to-istanbul@^9.0.1: + version "9.3.0" + resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz" + integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" + +value-or-function@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-4.0.0.tgz#70836b6a876a010dc3a2b884e7902e9db064378d" + integrity sha512-aeVK81SIuT6aMJfNo9Vte8Dw0/FZINGBV8BfCraGtqVxIeLAEhJyoWs8SmvRVmXfGss2PmmOwZCuBPbZR+IYWg== + +vfile-message@^4.0.0: + version "4.0.2" + resolved "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz" + integrity sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw== + dependencies: + "@types/unist" "^3.0.0" + unist-util-stringify-position "^4.0.0" + +vfile@^6.0.0: + version "6.0.3" + resolved "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz" + integrity sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q== + dependencies: + "@types/unist" "^3.0.0" + vfile-message "^4.0.0" + +vinyl-contents@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/vinyl-contents/-/vinyl-contents-2.0.0.tgz#cc2ba4db3a36658d069249e9e36d9e2b41935d89" + integrity sha512-cHq6NnGyi2pZ7xwdHSW1v4Jfnho4TEGtxZHw01cmnc8+i7jgR6bRnED/LbrKan/Q7CvVLbnvA5OepnhbpjBZ5Q== + dependencies: + bl "^5.0.0" + vinyl "^3.0.0" + +vinyl-fs@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-4.0.2.tgz#d46557653e4a7109f29d626a9cf478680c7f8c70" + integrity sha512-XRFwBLLTl8lRAOYiBqxY279wY46tVxLaRhSwo3GzKEuLz1giffsOquWWboD/haGf5lx+JyTigCFfe7DWHoARIA== + dependencies: + fs-mkdirp-stream "^2.0.1" + glob-stream "^8.0.3" + graceful-fs "^4.2.11" + iconv-lite "^0.6.3" + is-valid-glob "^1.0.0" + lead "^4.0.0" + normalize-path "3.0.0" + resolve-options "^2.0.0" + stream-composer "^1.0.2" + streamx "^2.14.0" + to-through "^3.0.0" + value-or-function "^4.0.0" + vinyl "^3.0.1" + vinyl-sourcemap "^2.0.0" + +vinyl-sourcemap@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/vinyl-sourcemap/-/vinyl-sourcemap-2.0.0.tgz#422f410a0ea97cb54cebd698d56a06d7a22e0277" + integrity sha512-BAEvWxbBUXvlNoFQVFVHpybBbjW1r03WhohJzJDSfgrrK5xVYIDTan6xN14DlyImShgDRv2gl9qhM6irVMsV0Q== + dependencies: + convert-source-map "^2.0.0" + graceful-fs "^4.2.10" + now-and-later "^3.0.0" + streamx "^2.12.5" + vinyl "^3.0.0" + vinyl-contents "^2.0.0" + +vinyl@^3.0.0, vinyl@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-3.0.1.tgz#5f5ff85255bda2b5da25e4b3bd80b3fc077fb5a9" + integrity sha512-0QwqXteBNXgnLCdWdvPQBX6FXRHtIH3VhJPTd5Lwn28tJXc34YqSCWUmkOvtJHBmB3gGoPtrOKk3Ts8/kEZ9aA== + dependencies: + clone "^2.1.2" + remove-trailing-separator "^1.1.0" + replace-ext "^2.0.0" + teex "^1.0.1" + +vite-compatible-readable-stream@^3.6.1: + version "3.6.1" + resolved "https://registry.npmjs.org/vite-compatible-readable-stream/-/vite-compatible-readable-stream-3.6.1.tgz" + integrity sha512-t20zYkrSf868+j/p31cRIGN28Phrjm3nRSLR2fyc2tiWi4cZGVdv68yNlwnIINTkMTmPoMiSlc0OadaO7DXZaQ== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +void-elements@3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz" + integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== + +w3c-xmlserializer@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz" + integrity sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw== + dependencies: + xml-name-validator "^4.0.0" + +walk-sync@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/walk-sync/-/walk-sync-2.2.0.tgz#80786b0657fcc8c0e1c0b1a042a09eae2966387a" + integrity sha512-IC8sL7aB4/ZgFcGI2T1LczZeFWZ06b3zoHH7jBPyHxOtIIz1jppWHjjEXkOFvFojBVAK9pV7g47xOZ4LW3QLfg== + dependencies: + "@types/minimatch" "^3.0.3" + ensure-posix-path "^1.1.0" + matcher-collection "^2.0.0" + minimatch "^3.0.4" + +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +warning@^4.0.3: + version "4.0.3" + resolved "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz" + integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== + dependencies: + loose-envify "^1.0.0" + +watchpack@^2.4.1: + version "2.4.2" + resolved "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz" + integrity sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + +web-vitals@^4.2.4: + version "4.2.4" + resolved "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz" + integrity sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw== + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +webidl-conversions@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz" + integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== + +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== + +webpack-sources@^1.4.3: + version "1.4.3" + resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz" + integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== + dependencies: + source-list-map "^2.0.0" + source-map "~0.6.1" + +webpack-sources@^3.2.3: + version "3.2.3" + resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== + +webpack-virtual-modules@^0.5.0: + version "0.5.0" + resolved "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.5.0.tgz" + integrity sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw== + +webpack@5.99.9: + version "5.99.9" + resolved "https://registry.npmjs.org/webpack/-/webpack-5.99.9.tgz" + integrity sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg== + dependencies: + "@types/eslint-scope" "^3.7.7" + "@types/estree" "^1.0.6" + "@types/json-schema" "^7.0.15" + "@webassemblyjs/ast" "^1.14.1" + "@webassemblyjs/wasm-edit" "^1.14.1" + "@webassemblyjs/wasm-parser" "^1.14.1" + acorn "^8.14.0" + browserslist "^4.24.0" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.17.1" + 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.11" + 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 "^4.3.2" + tapable "^2.1.1" + terser-webpack-plugin "^5.3.11" + watchpack "^2.4.1" + webpack-sources "^3.2.3" + +whatwg-encoding@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz" + integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg== + dependencies: + iconv-lite "0.6.3" + +whatwg-encoding@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz#d0f4ef769905d426e1688f3e34381a99b60b76e5" + integrity sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ== + dependencies: + iconv-lite "0.6.3" + +whatwg-mimetype@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz" + integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== + +whatwg-mimetype@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz#bc1bf94a985dc50388d54a9258ac405c3ca2fc0a" + integrity sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg== + +whatwg-url@^11.0.0: + version "11.0.0" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz" + integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ== + dependencies: + tr46 "^3.0.0" + webidl-conversions "^7.0.0" + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +whatwg-url@^6.5.0: + version "6.5.0" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz" + integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ== + dependencies: + lodash.sortby "^4.7.0" + tr46 "^1.0.1" + webidl-conversions "^4.0.2" + +whatwg-url@^7.0.0: + version "7.1.0" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz" + integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg== + dependencies: + lodash.sortby "^4.7.0" + tr46 "^1.0.1" + webidl-conversions "^4.0.2" + +which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz" + integrity sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA== + dependencies: + is-bigint "^1.1.0" + is-boolean-object "^1.2.1" + is-number-object "^1.1.1" + is-string "^1.1.1" + is-symbol "^1.1.1" + +which-builtin-type@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz" + integrity sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q== + dependencies: + call-bound "^1.0.2" + function.prototype.name "^1.1.6" + has-tostringtag "^1.0.2" + is-async-function "^2.0.0" + is-date-object "^1.1.0" + is-finalizationregistry "^1.1.0" + is-generator-function "^1.0.10" + is-regex "^1.2.1" + is-weakref "^1.0.2" + isarray "^2.0.5" + which-boxed-primitive "^1.1.0" + which-collection "^1.0.2" + which-typed-array "^1.1.16" + +which-collection@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz" + integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== + dependencies: + is-map "^2.0.3" + is-set "^2.0.3" + is-weakmap "^2.0.2" + is-weakset "^2.0.3" + +which-typed-array@^1.1.16, which-typed-array@^1.1.18: + version "1.1.19" + resolved "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz" + integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.4" + for-each "^0.3.5" + get-proto "^1.0.1" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + +which@^1.3.1: + version "1.3.1" + resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +which@^2.0.1, which@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +workbox-background-sync@7.1.0: + version "7.1.0" + resolved "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.1.0.tgz" + integrity sha512-rMbgrzueVWDFcEq1610YyDW71z0oAXLfdRHRQcKw4SGihkfOK0JUEvqWHFwA6rJ+6TClnMIn7KQI5PNN1XQXwQ== + dependencies: + idb "^7.0.1" + workbox-core "7.1.0" + +workbox-broadcast-update@7.1.0: + version "7.1.0" + resolved "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-7.1.0.tgz" + integrity sha512-O36hIfhjej/c5ar95pO67k1GQw0/bw5tKP7CERNgK+JdxBANQhDmIuOXZTNvwb2IHBx9hj2kxvcDyRIh5nzOgQ== + dependencies: + workbox-core "7.1.0" + +workbox-build@7.1.0: + version "7.1.0" + resolved "https://registry.npmjs.org/workbox-build/-/workbox-build-7.1.0.tgz" + integrity sha512-F6R94XAxjB2j4ETMkP1EXKfjECOtDmyvt0vz3BzgWJMI68TNSXIVNkgatwUKBlPGOfy9n2F/4voYRNAhEvPJNg== + dependencies: + "@apideck/better-ajv-errors" "^0.3.1" + "@babel/core" "^7.24.4" + "@babel/preset-env" "^7.11.0" + "@babel/runtime" "^7.11.2" + "@rollup/plugin-babel" "^5.2.0" + "@rollup/plugin-node-resolve" "^15.2.3" + "@rollup/plugin-replace" "^2.4.1" + "@rollup/plugin-terser" "^0.4.3" + "@surma/rollup-plugin-off-main-thread" "^2.2.3" + ajv "^8.6.0" + common-tags "^1.8.0" + fast-json-stable-stringify "^2.1.0" + fs-extra "^9.0.1" + glob "^7.1.6" + lodash "^4.17.20" + pretty-bytes "^5.3.0" + rollup "^2.43.1" + source-map "^0.8.0-beta.0" + stringify-object "^3.3.0" + strip-comments "^2.0.1" + tempy "^0.6.0" + upath "^1.2.0" + workbox-background-sync "7.1.0" + workbox-broadcast-update "7.1.0" + workbox-cacheable-response "7.1.0" + workbox-core "7.1.0" + workbox-expiration "7.1.0" + workbox-google-analytics "7.1.0" + workbox-navigation-preload "7.1.0" + workbox-precaching "7.1.0" + workbox-range-requests "7.1.0" + workbox-recipes "7.1.0" + workbox-routing "7.1.0" + workbox-strategies "7.1.0" + workbox-streams "7.1.0" + workbox-sw "7.1.0" + workbox-window "7.1.0" + +workbox-cacheable-response@7.1.0: + version "7.1.0" + resolved "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-7.1.0.tgz" + integrity sha512-iwsLBll8Hvua3xCuBB9h92+/e0wdsmSVgR2ZlvcfjepZWwhd3osumQB3x9o7flj+FehtWM2VHbZn8UJeBXXo6Q== + dependencies: + workbox-core "7.1.0" + +workbox-core@7.1.0: + version "7.1.0" + resolved "https://registry.npmjs.org/workbox-core/-/workbox-core-7.1.0.tgz" + integrity sha512-5KB4KOY8rtL31nEF7BfvU7FMzKT4B5TkbYa2tzkS+Peqj0gayMT9SytSFtNzlrvMaWgv6y/yvP9C0IbpFjV30Q== + +workbox-expiration@7.1.0: + version "7.1.0" + resolved "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-7.1.0.tgz" + integrity sha512-m5DcMY+A63rJlPTbbBNtpJ20i3enkyOtSgYfv/l8h+D6YbbNiA0zKEkCUaMsdDlxggla1oOfRkyqTvl5Ni5KQQ== + dependencies: + idb "^7.0.1" + workbox-core "7.1.0" + +workbox-google-analytics@7.1.0: + version "7.1.0" + resolved "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-7.1.0.tgz" + integrity sha512-FvE53kBQHfVTcZyczeBVRexhh7JTkyQ8HAvbVY6mXd2n2A7Oyz/9fIwnY406ZcDhvE4NFfKGjW56N4gBiqkrew== + dependencies: + workbox-background-sync "7.1.0" + workbox-core "7.1.0" + workbox-routing "7.1.0" + workbox-strategies "7.1.0" + +workbox-navigation-preload@7.1.0: + version "7.1.0" + resolved "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-7.1.0.tgz" + integrity sha512-4wyAbo0vNI/X0uWNJhCMKxnPanNyhybsReMGN9QUpaePLTiDpKxPqFxl4oUmBNddPwIXug01eTSLVIFXimRG/A== + dependencies: + workbox-core "7.1.0" + +workbox-precaching@7.1.0: + version "7.1.0" + resolved "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-7.1.0.tgz" + integrity sha512-LyxzQts+UEpgtmfnolo0hHdNjoB7EoRWcF7EDslt+lQGd0lW4iTvvSe3v5JiIckQSB5KTW5xiCqjFviRKPj1zA== + dependencies: + workbox-core "7.1.0" + workbox-routing "7.1.0" + workbox-strategies "7.1.0" + +workbox-range-requests@7.1.0: + version "7.1.0" + resolved "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-7.1.0.tgz" + integrity sha512-m7+O4EHolNs5yb/79CrnwPR/g/PRzMFYEdo01LqwixVnc/sbzNSvKz0d04OE3aMRel1CwAAZQheRsqGDwATgPQ== + dependencies: + workbox-core "7.1.0" + +workbox-recipes@7.1.0: + version "7.1.0" + resolved "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-7.1.0.tgz" + integrity sha512-NRrk4ycFN9BHXJB6WrKiRX3W3w75YNrNrzSX9cEZgFB5ubeGoO8s/SDmOYVrFYp9HMw6sh1Pm3eAY/1gVS8YLg== + dependencies: + workbox-cacheable-response "7.1.0" + workbox-core "7.1.0" + workbox-expiration "7.1.0" + workbox-precaching "7.1.0" + workbox-routing "7.1.0" + workbox-strategies "7.1.0" + +workbox-routing@7.1.0: + version "7.1.0" + resolved "https://registry.npmjs.org/workbox-routing/-/workbox-routing-7.1.0.tgz" + integrity sha512-oOYk+kLriUY2QyHkIilxUlVcFqwduLJB7oRZIENbqPGeBP/3TWHYNNdmGNhz1dvKuw7aqvJ7CQxn27/jprlTdg== + dependencies: + workbox-core "7.1.0" + +workbox-strategies@7.1.0: + version "7.1.0" + resolved "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-7.1.0.tgz" + integrity sha512-/UracPiGhUNehGjRm/tLUQ+9PtWmCbRufWtV0tNrALuf+HZ4F7cmObSEK+E4/Bx1p8Syx2tM+pkIrvtyetdlew== + dependencies: + workbox-core "7.1.0" + +workbox-streams@7.1.0: + version "7.1.0" + resolved "https://registry.npmjs.org/workbox-streams/-/workbox-streams-7.1.0.tgz" + integrity sha512-WyHAVxRXBMfysM8ORwiZnI98wvGWTVAq/lOyBjf00pXFvG0mNaVz4Ji+u+fKa/mf1i2SnTfikoYKto4ihHeS6w== + dependencies: + workbox-core "7.1.0" + workbox-routing "7.1.0" + +workbox-sw@7.1.0: + version "7.1.0" + resolved "https://registry.npmjs.org/workbox-sw/-/workbox-sw-7.1.0.tgz" + integrity sha512-Hml/9+/njUXBglv3dtZ9WBKHI235AQJyLBV1G7EFmh4/mUdSQuXui80RtjDeVRrXnm/6QWgRUEHG3/YBVbxtsA== + +workbox-webpack-plugin@7.1.0: + version "7.1.0" + resolved "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-7.1.0.tgz" + integrity sha512-em0vY0Uq7zXzOeEJYpFNX7x6q3RrRVqfaMhA4kadd3UkX/JuClgT9IUW2iX2cjmMPwI3W611c4fSRjtG5wPm2w== + dependencies: + fast-json-stable-stringify "^2.1.0" + pretty-bytes "^5.4.1" + upath "^1.2.0" + webpack-sources "^1.4.3" + workbox-build "7.1.0" + +workbox-window@7.1.0: + version "7.1.0" + resolved "https://registry.npmjs.org/workbox-window/-/workbox-window-7.1.0.tgz" + integrity sha512-ZHeROyqR+AS5UPzholQRDttLFqGMwP0Np8MKWAdyxsDETxq3qOAyXvqessc3GniohG6e0mAqSQyKOHmT8zPF7g== + dependencies: + "@types/trusted-types" "^2.0.2" + workbox-core "7.1.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-9.0.0.tgz#1a3dc8b70d85eeb8398ddfb1e4a02cd186e58b3e" + integrity sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q== + dependencies: + ansi-styles "^6.2.1" + string-width "^7.0.0" + strip-ansi "^7.1.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +write-file-atomic@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz" + integrity sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^4.0.1" + +ws@^8.11.0: + version "8.18.2" + resolved "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz" + integrity sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ== + +xml-name-validator@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz" + integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw== + +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + +xtend@^4.0.0, xtend@~4.0.1: + version "4.0.2" + resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yaml@^1.10.0: + version "1.10.2" + resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs-parser@^22.0.0: + version "22.0.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-22.0.0.tgz#87b82094051b0567717346ecd00fd14804b357c8" + integrity sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw== + +yargs@18.0.0: + version "18.0.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-18.0.0.tgz#6c84259806273a746b09f579087b68a3c2d25bd1" + integrity sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg== + dependencies: + cliui "^9.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + string-width "^7.2.0" + y18n "^5.0.5" + yargs-parser "^22.0.0" + +yargs@^17.3.1: + version "17.7.2" + resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +yoga-layout@^3.2.1: + version "3.2.1" + resolved "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz" + integrity sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ== + +zod-to-json-schema@^3.24.1: + version "3.24.5" + resolved "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz" + integrity sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g== + +zod@^3.25.67: + version "3.25.67" + resolved "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz" + integrity sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw== + +zustand@5.0.5: + version "5.0.5" + resolved "https://registry.npmjs.org/zustand/-/zustand-5.0.5.tgz" + integrity sha512-mILtRfKW9xM47hqxGIxCv12gXusoY/xTSHBYApXozR0HmQv299whhBeeAcRy+KrPPybzosvJBCOmVjq6x12fCg== + +zwitch@^2.0.0: + version "2.0.4" + resolved "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz" + integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A== diff --git a/src/helm/conversations/Chart.yaml b/src/helm/conversations/Chart.yaml new file mode 100644 index 0000000..d391571 --- /dev/null +++ b/src/helm/conversations/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v2 +type: application +name: conversations +version: 0.0.1 +appVersion: latest diff --git a/src/helm/conversations/README.md b/src/helm/conversations/README.md new file mode 100644 index 0000000..7cdf713 --- /dev/null +++ b/src/helm/conversations/README.md @@ -0,0 +1,205 @@ +# Conversations helm chart + +## Parameters + +### General configuration + +| Name | Description | Value | +|-------------------------------------------------------------------------------------|-----------------------------------------------------------|--------------------------------------------------------------------| +| `image.repository` | Repository to use to pull conversations's container image | `lasuite/conversations-backend` | +| `image.tag` | conversations's container tag | `latest` | +| `image.pullPolicy` | Container image pull policy | `IfNotPresent` | +| `image.credentials.username` | Username for container registry authentication | | +| `image.credentials.password` | Password for container registry authentication | | +| `image.credentials.registry` | Registry url for which the credentials are specified | | +| `image.credentials.name` | Name of the generated secret for imagePullSecrets | | +| `nameOverride` | Override the chart name | `""` | +| `fullnameOverride` | Override the full application name | `""` | +| `ingress.enabled` | whether to enable the Ingress or not | `false` | +| `ingress.className` | IngressClass to use for the Ingress | `nil` | +| `ingress.host` | Host for the Ingress | `conversations.example.com` | +| `ingress.path` | Path to use for the Ingress | `/` | +| `ingress.hosts` | Additional host to configure for the Ingress | `[]` | +| `ingress.tls.enabled` | Weather to enable TLS for the Ingress | `true` | +| `ingress.tls.secretName` | Secret name for TLS config | `nil` | +| `ingress.tls.additional[].secretName` | Secret name for additional TLS config | | +| `ingress.tls.additional[].hosts[]` | Hosts for additional TLS config | | +| `ingress.customBackends` | Add custom backends to ingress | `[]` | +| `ingressAdmin.enabled` | whether to enable the Ingress or not | `false` | +| `ingressAdmin.className` | IngressClass to use for the Ingress | `nil` | +| `ingressAdmin.host` | Host for the Ingress | `conversations.example.com` | +| `ingressAdmin.path` | Path to use for the Ingress | `/admin` | +| `ingressAdmin.hosts` | Additional host to configure for the Ingress | `[]` | +| `ingressAdmin.tls.enabled` | Weather to enable TLS for the Ingress | `true` | +| `ingressAdmin.tls.secretName` | Secret name for TLS config | `nil` | +| `ingressAdmin.tls.additional[].secretName` | Secret name for additional TLS config | | +| `ingressAdmin.tls.additional[].hosts[]` | Hosts for additional TLS config | | +| `ingressMedia.enabled` | whether to enable the Ingress or not | `false` | +| `ingressMedia.className` | IngressClass to use for the Ingress | `nil` | +| `ingressMedia.host` | Host for the Ingress | `conversations.example.com` | +| `ingressMedia.path` | Path to use for the Ingress | `/media/(.*)` | +| `ingressMedia.hosts` | Additional host to configure for the Ingress | `[]` | +| `ingressMedia.tls.enabled` | Weather to enable TLS for the Ingress | `true` | +| `ingressMedia.tls.secretName` | Secret name for TLS config | `nil` | +| `ingressMedia.tls.additional[].secretName` | Secret name for additional TLS config | | +| `ingressMedia.tls.additional[].hosts[]` | Hosts for additional TLS config | | +| `ingressMedia.annotations.nginx.ingress.kubernetes.io/auth-url` | | `https://conversations.example.com/api/v1.0/documents/media-auth/` | +| `ingressMedia.annotations.nginx.ingress.kubernetes.io/auth-response-headers` | | `Authorization, X-Amz-Date, X-Amz-Content-SHA256` | +| `ingressMedia.annotations.nginx.ingress.kubernetes.io/upstream-vhost` | | `minio.conversations.svc.cluster.local:9000` | +| `ingressMedia.annotations.nginx.ingress.kubernetes.io/configuration-snippet` | | `add_header Content-Security-Policy "default-src 'none'" always; | +| ` | | | +| `serviceMedia.host` | | `minio.conversations.svc.cluster.local` | +| `serviceMedia.port` | | `9000` | +| `serviceMedia.annotations` | | `{}` | + +### backend + +| Name | Description | Value | +| ----------------------------------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `backend.command` | Override the backend container command | `[]` | +| `backend.args` | Override the backend container args | `[]` | +| `backend.replicas` | Amount of backend replicas | `3` | +| `backend.shareProcessNamespace` | Enable share process namespace between containers | `false` | +| `backend.sidecars` | Add sidecars containers to backend deployment | `[]` | +| `backend.migrateJobAnnotations` | Annotations for the migrate job | `{}` | +| `backend.jobs.ttlSecondsAfterFinished` | Period to wait before remove jobs | `30` | +| `backend.jobs.backoffLimit` | Numbers of jobs retries | `2` | +| `backend.securityContext` | Configure backend Pod security context | `nil` | +| `backend.envVars` | Configure backend container environment variables | `undefined` | +| `backend.envVars.BY_VALUE` | Example environment variable by setting value directly | | +| `backend.envVars.FROM_CONFIGMAP.configMapKeyRef.name` | Name of a ConfigMap when configuring env vars from a ConfigMap | | +| `backend.envVars.FROM_CONFIGMAP.configMapKeyRef.key` | Key within a ConfigMap when configuring env vars from a ConfigMap | | +| `backend.envVars.FROM_SECRET.secretKeyRef.name` | Name of a Secret when configuring env vars from a Secret | | +| `backend.envVars.FROM_SECRET.secretKeyRef.key` | Key within a Secret when configuring env vars from a Secret | | +| `backend.podAnnotations` | Annotations to add to the backend Pod | `{}` | +| `backend.dpAnnotations` | Annotations to add to the backend Deployment | `{}` | +| `backend.service.type` | backend Service type | `ClusterIP` | +| `backend.service.port` | backend Service listening port | `80` | +| `backend.service.targetPort` | backend container listening port | `8000` | +| `backend.service.annotations` | Annotations to add to the backend Service | `{}` | +| `backend.migrate.command` | backend migrate command | `["/bin/sh","-c","while ! python manage.py check --database default > /dev/null 2>&1\ndo\n echo \"Database not ready\"\n sleep 2\ndone\n\necho \"Database is ready\"\n\npython manage.py migrate --no-input\n"]` | +| `backend.migrate.restartPolicy` | backend migrate job restart policy | `Never` | +| `backend.createsuperuser.command` | backend migrate command | `["/bin/sh","-c","while ! python manage.py check --database default > /dev/null 2>&1\ndo\n echo \"Database not ready\"\n sleep 2\ndone\n\necho \"Database is ready\"\n\npython manage.py createsuperuser --email $DJANGO_SUPERUSER_EMAIL --password $DJANGO_SUPERUSER_PASSWORD\n"]` | +| `backend.createsuperuser.restartPolicy` | backend migrate job restart policy | `Never` | +| `backend.job` | job dedicated to run a random management command, for example after a deployment | | +| `backend.job.name` | The name to use to describe this job | `""` | +| `backend.job.command` | The management command to execute | `[]` | +| `backend.job.restartPolicy` | The restart policy for the job. | `Never` | +| `backend.job.annotations` | Annotations to add to the job [default: argocd.argoproj.io/hook: PostSync] | | +| `backend.probes.liveness.path` | Configure path for backend HTTP liveness probe | `/__heartbeat__` | +| `backend.probes.liveness.targetPort` | Configure port for backend HTTP liveness probe | `undefined` | +| `backend.probes.liveness.initialDelaySeconds` | Configure initial delay for backend liveness probe | `10` | +| `backend.probes.liveness.initialDelaySeconds` | Configure timeout for backend liveness probe | `10` | +| `backend.probes.startup.path` | Configure path for backend HTTP startup probe | `undefined` | +| `backend.probes.startup.targetPort` | Configure port for backend HTTP startup probe | `undefined` | +| `backend.probes.startup.initialDelaySeconds` | Configure initial delay for backend startup probe | `undefined` | +| `backend.probes.startup.initialDelaySeconds` | Configure timeout for backend startup probe | `undefined` | +| `backend.probes.readiness.path` | Configure path for backend HTTP readiness probe | `/__lbheartbeat__` | +| `backend.probes.readiness.targetPort` | Configure port for backend HTTP readiness probe | `undefined` | +| `backend.probes.readiness.initialDelaySeconds` | Configure initial delay for backend readiness probe | `10` | +| `backend.probes.readiness.initialDelaySeconds` | Configure timeout for backend readiness probe | `10` | +| `backend.resources` | Resource requirements for the backend container | `{}` | +| `backend.nodeSelector` | Node selector for the backend Pod | `{}` | +| `backend.tolerations` | Tolerations for the backend Pod | `[]` | +| `backend.affinity` | Affinity for the backend Pod | `{}` | +| `backend.persistence` | Additional volumes to create and mount on the backend. Used for debugging purposes | `{}` | +| `backend.persistence.volume-name.size` | Size of the additional volume | | +| `backend.persistence.volume-name.type` | Type of the additional volume, persistentVolumeClaim or emptyDir | | +| `backend.persistence.volume-name.mountPath` | Path where the volume should be mounted to | | +| `backend.extraVolumeMounts` | Additional volumes to mount on the backend. | `[]` | +| `backend.extraVolumes` | Additional volumes to mount on the backend. | `[]` | +| `backend.pdb.enabled` | Enable pdb on backend | `true` | +| `backend.themeCustomization.enabled` | Enable theme customization | `false` | +| `backend.themeCustomization.file_content` | Content of the theme customization file. Must be a json object. | `""` | +| `backend.themeCustomization.mount_path` | Path where the customization file will be mounted in the backend deployment. | `/app/conversations/configuration/theme` | +| `backend.celery.replicas` | Amount of celery replicas | `1` | +| `backend.celery.command` | Override the celery container command | `[]` | +| `backend.celery.args` | Override the celery container args | `["celery","-A","conversations.celery_app","worker","-l","INFO","-n","conversations@%h"]` | +| `backend.celery.resources` | Resource requirements for the celery container | `{}` | +| `backend.celery.probes.liveness.exec.command` | Override the celery container liveness probe command | `["/bin/sh","-c","celery -A conversations.celery_app inspect ping -d conversations@$HOSTNAME"]` | +| `backend.celery.probes.liveness.initialDelaySeconds` | Initial delay for the celery container liveness probe | `60` | +| `backend.celery.probes.liveness.timeoutSeconds` | Timeout for the celery container liveness probe | `5` | +| `backend.celery.probes.readiness.exec.command` | Override the celery container readiness probe command | `["/bin/sh","-c","celery -A conversations.celery_app inspect ping -d conversations@$HOSTNAME"]` | +| `backend.celery.probes.readiness.initialDelaySeconds` | Initial delay for the celery container readiness probe | `15` | +| `backend.celery.probes.readiness.timeoutSeconds` | Timeout for the celery container readiness probe | `5` | + +### frontend + +| Name | Description | Value | +| ------------------------------------------------------ | ----------------------------------------------------------------------------------- | -------------------------- | +| `frontend.image.repository` | Repository to use to pull conversations's frontend container image | `lasuite/conversations-frontend` | +| `frontend.image.tag` | conversations's frontend container tag | `latest` | +| `frontend.image.pullPolicy` | frontend container image pull policy | `IfNotPresent` | +| `frontend.command` | Override the frontend container command | `[]` | +| `frontend.args` | Override the frontend container args | `[]` | +| `frontend.replicas` | Amount of frontend replicas | `3` | +| `frontend.shareProcessNamespace` | Enable share process namefrontend between containers | `false` | +| `frontend.sidecars` | Add sidecars containers to frontend deployment | `[]` | +| `frontend.securityContext` | Configure frontend Pod security context | `nil` | +| `frontend.envVars` | Configure frontend container environment variables | `undefined` | +| `frontend.envVars.BY_VALUE` | Example environment variable by setting value directly | | +| `frontend.envVars.FROM_CONFIGMAP.configMapKeyRef.name` | Name of a ConfigMap when configuring env vars from a ConfigMap | | +| `frontend.envVars.FROM_CONFIGMAP.configMapKeyRef.key` | Key within a ConfigMap when configuring env vars from a ConfigMap | | +| `frontend.envVars.FROM_SECRET.secretKeyRef.name` | Name of a Secret when configuring env vars from a Secret | | +| `frontend.envVars.FROM_SECRET.secretKeyRef.key` | Key within a Secret when configuring env vars from a Secret | | +| `frontend.podAnnotations` | Annotations to add to the frontend Pod | `{}` | +| `frontend.dpAnnotations` | Annotations to add to the frontend Deployment | `{}` | +| `frontend.service.type` | frontend Service type | `ClusterIP` | +| `frontend.service.port` | frontend Service listening port | `80` | +| `frontend.service.targetPort` | frontend container listening port | `8080` | +| `frontend.service.annotations` | Annotations to add to the frontend Service | `{}` | +| `frontend.probes` | Configure probe for frontend | `{}` | +| `frontend.probes.liveness.path` | Configure path for frontend HTTP liveness probe | | +| `frontend.probes.liveness.targetPort` | Configure port for frontend HTTP liveness probe | | +| `frontend.probes.liveness.initialDelaySeconds` | Configure initial delay for frontend liveness probe | | +| `frontend.probes.liveness.initialDelaySeconds` | Configure timeout for frontend liveness probe | | +| `frontend.probes.startup.path` | Configure path for frontend HTTP startup probe | | +| `frontend.probes.startup.targetPort` | Configure port for frontend HTTP startup probe | | +| `frontend.probes.startup.initialDelaySeconds` | Configure initial delay for frontend startup probe | | +| `frontend.probes.startup.initialDelaySeconds` | Configure timeout for frontend startup probe | | +| `frontend.probes.readiness.path` | Configure path for frontend HTTP readiness probe | | +| `frontend.probes.readiness.targetPort` | Configure port for frontend HTTP readiness probe | | +| `frontend.probes.readiness.initialDelaySeconds` | Configure initial delay for frontend readiness probe | | +| `frontend.probes.readiness.initialDelaySeconds` | Configure timeout for frontend readiness probe | | +| `frontend.resources` | Resource requirements for the frontend container | `{}` | +| `frontend.nodeSelector` | Node selector for the frontend Pod | `{}` | +| `frontend.tolerations` | Tolerations for the frontend Pod | `[]` | +| `frontend.affinity` | Affinity for the frontend Pod | `{}` | +| `frontend.persistence` | Additional volumes to create and mount on the frontend. Used for debugging purposes | `{}` | +| `frontend.persistence.volume-name.size` | Size of the additional volume | | +| `frontend.persistence.volume-name.type` | Type of the additional volume, persistentVolumeClaim or emptyDir | | +| `frontend.persistence.volume-name.mountPath` | Path where the volume should be mounted to | | +| `frontend.extraVolumeMounts` | Additional volumes to mount on the frontend. | `[]` | +| `frontend.extraVolumes` | Additional volumes to mount on the frontend. | `[]` | +| `frontend.pdb.enabled` | Enable pdb on frontend | `true` | + +### posthog + +| Name | Description | Value | +| -------------------------------------- | ----------------------------------------------------------- | ------------------------- | +| `posthog.ingress.enabled` | Enable or disable the ingress resource creation | `false` | +| `posthog.ingress.className` | Kubernetes ingress class name to use (e.g., nginx, traefik) | `nil` | +| `posthog.ingress.host` | Primary hostname for the ingress resource | `conversations.example.com` | +| `posthog.ingress.path` | URL path prefix for the ingress routes (e.g., /) | `/` | +| `posthog.ingress.hosts` | Additional hostnames array to be included in the ingress | `[]` | +| `posthog.ingress.tls.enabled` | Enable or disable TLS/HTTPS for the ingress | `true` | +| `posthog.ingress.tls.additional` | Additional TLS configurations for extra hosts/certificates | `[]` | +| `posthog.ingress.customBackends` | Custom backend service configurations for the ingress | `[]` | +| `posthog.ingress.annotations` | Additional Kubernetes annotations to apply to the ingress | `{}` | +| `posthog.ingressAssets.enabled` | Enable or disable the ingress resource creation | `false` | +| `posthog.ingressAssets.className` | Kubernetes ingress class name to use (e.g., nginx, traefik) | `nil` | +| `posthog.ingressAssets.host` | Primary hostname for the ingress resource | `conversations.example.com` | +| `posthog.ingressAssets.paths` | URL paths prefix for the ingress routes (e.g., /static) | `["/static","/array"]` | +| `posthog.ingressAssets.hosts` | Additional hostnames array to be included in the ingress | `[]` | +| `posthog.ingressAssets.tls.enabled` | Enable or disable TLS/HTTPS for the ingress | `true` | +| `posthog.ingressAssets.tls.additional` | Additional TLS configurations for extra hosts/certificates | `[]` | +| `posthog.ingressAssets.customBackends` | Custom backend service configurations for the ingress | `[]` | +| `posthog.ingressAssets.annotations` | Additional Kubernetes annotations to apply to the ingress | `{}` | +| `posthog.service.type` | Service type (e.g. ExternalName, ClusterIP, LoadBalancer) | `ExternalName` | +| `posthog.service.externalName` | External service hostname when type is ExternalName | `eu.i.posthog.com` | +| `posthog.service.port` | Port number for the service | `443` | +| `posthog.service.annotations` | Additional annotations to apply to the service | `{}` | +| `posthog.assetsService.type` | Service type (e.g. ExternalName, ClusterIP, LoadBalancer) | `ExternalName` | +| `posthog.assetsService.externalName` | External service hostname when type is ExternalName | `eu-assets.i.posthog.com` | +| `posthog.assetsService.port` | Port number for the service | `443` | +| `posthog.assetsService.annotations` | Additional annotations to apply to the service | `{}` | diff --git a/src/helm/conversations/generate-readme.sh b/src/helm/conversations/generate-readme.sh new file mode 100755 index 0000000..cc689ba --- /dev/null +++ b/src/helm/conversations/generate-readme.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +docker image ls | grep readme-generator-for-helm +if [ "$?" -ne "0" ]; then + git clone https://github.com/bitnami/readme-generator-for-helm.git /tmp/readme-generator-for-helm + cd /tmp/readme-generator-for-helm + docker build -t readme-generator-for-helm:latest . + cd $(dirname -- "${BASH_SOURCE[0]}") +fi +docker run --rm -it -v .:/source -w /source readme-generator-for-helm:latest readme-generator -v values.yaml -r README.md diff --git a/src/helm/conversations/templates/_helpers.tpl b/src/helm/conversations/templates/_helpers.tpl new file mode 100644 index 0000000..640e850 --- /dev/null +++ b/src/helm/conversations/templates/_helpers.tpl @@ -0,0 +1,185 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "conversations.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "conversations.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "conversations.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +conversations.labels +*/}} +{{- define "conversations.labels" -}} +helm.sh/chart: {{ include "conversations.chart" . }} +{{ include "conversations.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "conversations.selectorLabels" -}} +app.kubernetes.io/name: {{ include "conversations.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +transform dictionary of environment variables +Usage : {{ include "conversations.env.transformDict" .Values.envVars }} + +Example: +envVars: + # Using simple strings as env vars + ENV_VAR_NAME: "envVar value" + # Using a value from a configMap + ENV_VAR_FROM_CM: + configMapKeyRef: + name: cm-name + key: "key_in_cm" + # Using a value from a secret + ENV_VAR_FROM_SECRET: + secretKeyRef: + name: secret-name + key: "key_in_secret" +*/}} +{{- define "conversations.env.transformDict" -}} +{{- range $key, $value := . }} +- name: {{ $key | quote }} +{{- if $value | kindIs "map" }} + valueFrom: {{ $value | toYaml | nindent 4 }} +{{- else }} + value: {{ $value | quote }} +{{- end }} +{{- end }} +{{- end }} + + +{{/* +conversations env vars +*/}} +{{- define "conversations.common.env" -}} +{{- $topLevelScope := index . 0 -}} +{{- $workerScope := index . 1 -}} +{{- include "conversations.env.transformDict" $workerScope.envVars -}} +{{- end }} + +{{/* +Common labels + +Requires array with top level scope and component name +*/}} +{{- define "conversations.common.labels" -}} +{{- $topLevelScope := index . 0 -}} +{{- $component := index . 1 -}} +{{- include "conversations.labels" $topLevelScope }} +app.kubernetes.io/component: {{ $component }} +{{- end }} + +{{/* +Common selector labels + +Requires array with top level scope and component name +*/}} +{{- define "conversations.common.selectorLabels" -}} +{{- $topLevelScope := index . 0 -}} +{{- $component := index . 1 -}} +{{- include "conversations.selectorLabels" $topLevelScope }} +app.kubernetes.io/component: {{ $component }} +{{- end }} + +{{- define "conversations.probes.abstract" -}} +{{- if .exec -}} +exec: +{{- toYaml .exec | nindent 2 }} +{{- else if .tcpSocket -}} +tcpSocket: +{{- toYaml .tcpSocket | nindent 2 }} +{{- else -}} +httpGet: + path: {{ .path }} + port: {{ .targetPort }} +{{- end }} +initialDelaySeconds: {{ .initialDelaySeconds | eq nil | ternary 0 .initialDelaySeconds }} +timeoutSeconds: {{ .timeoutSeconds | eq nil | ternary 1 .timeoutSeconds }} +{{- end }} + +{{/* +Full name for the backend + +Requires top level scope +*/}} +{{- define "conversations.backend.fullname" -}} +{{ include "conversations.fullname" . }}-backend +{{- end }} + +{{/* +Full name for the frontend + +Requires top level scope +*/}} +{{- define "conversations.frontend.fullname" -}} +{{ include "conversations.fullname" . }}-frontend +{{- end }} + +{{/* +Full name for the Posthog + +Requires top level scope +*/}} +{{- define "conversations.posthog.fullname" -}} +{{ include "conversations.fullname" . }}-posthog +{{- end }} + + +{{/* +Usage : {{ include "conversations.secret.dockerconfigjson.name" (dict "fullname" (include "conversations.fullname" .) "imageCredentials" .Values.path.to.the.image1) }} +*/}} +{{- define "conversations.secret.dockerconfigjson.name" }} +{{- if (default (dict) .imageCredentials).name }}{{ .imageCredentials.name }}{{ else }}{{ .fullname | trunc 63 | trimSuffix "-" }}-dockerconfig{{ end -}} +{{- end }} + +{{/* +Usage : {{ include "conversations.secret.dockerconfigjson" (dict "fullname" (include "conversations.fullname" .) "imageCredentials" .Values.path.to.the.image1) }} +*/}} +{{- define "conversations.secret.dockerconfigjson" }} +{{- if .imageCredentials -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "conversations.secret.dockerconfigjson.name" (dict "fullname" .fullname "imageCredentials" .imageCredentials) }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-weight": "-5" + "helm.sh/hook-delete-policy": before-hook-creation +type: kubernetes.io/dockerconfigjson +data: + .dockerconfigjson: {{ template "conversations.secret.dockerconfigjson.data" .imageCredentials }} +{{- end -}} +{{- end }} diff --git a/src/helm/conversations/templates/backend_deployment.yaml b/src/helm/conversations/templates/backend_deployment.yaml new file mode 100644 index 0000000..cd87686 --- /dev/null +++ b/src/helm/conversations/templates/backend_deployment.yaml @@ -0,0 +1,163 @@ +{{- $envVars := include "conversations.common.env" (list . .Values.backend) -}} +{{- $fullName := include "conversations.backend.fullname" . -}} +{{- $component := "backend" -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ $fullName }} + namespace: {{ .Release.Namespace | quote }} + annotations: + {{- with .Values.backend.dpAnnotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + labels: + {{- include "conversations.common.labels" (list . $component) | nindent 4 }} +spec: + replicas: {{ .Values.backend.replicas }} + selector: + matchLabels: + {{- include "conversations.common.selectorLabels" (list . $component) | nindent 6 }} + template: + metadata: + annotations: + {{- with .Values.backend.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "conversations.common.selectorLabels" (list . $component) | nindent 8 }} + spec: + {{- if $.Values.image.credentials }} + imagePullSecrets: + - name: {{ include "conversations.secret.dockerconfigjson.name" (dict "fullname" (include "conversations.fullname" .) "imageCredentials" $.Values.image.credentials) }} + {{- end}} + shareProcessNamespace: {{ .Values.backend.shareProcessNamespace }} + containers: + {{- with .Values.backend.sidecars }} + {{- toYaml . | nindent 8 }} + {{- end }} + - name: {{ .Chart.Name }} + image: "{{ (.Values.backend.image | default dict).repository | default .Values.image.repository }}:{{ (.Values.backend.image | default dict).tag | default .Values.image.tag }}" + imagePullPolicy: {{ (.Values.backend.image | default dict).pullPolicy | default .Values.image.pullPolicy }} + {{- with .Values.backend.command }} + command: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.backend.args }} + args: + {{- toYaml . | nindent 12 }} + {{- end }} + env: + {{- if $envVars}} + {{- $envVars | indent 12 }} + {{- end }} + {{- with .Values.backend.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: http + containerPort: {{ .Values.backend.service.targetPort }} + protocol: TCP + {{- if .Values.backend.probes.liveness }} + livenessProbe: + {{- include "conversations.probes.abstract" (merge .Values.backend.probes.liveness (dict "targetPort" .Values.backend.service.targetPort )) | nindent 12 }} + {{- end }} + {{- if .Values.backend.probes.readiness }} + readinessProbe: + {{- include "conversations.probes.abstract" (merge .Values.backend.probes.readiness (dict "targetPort" .Values.backend.service.targetPort )) | nindent 12 }} + {{- end }} + {{- if .Values.backend.probes.startup }} + startupProbe: + {{- include "conversations.probes.abstract" (merge .Values.backend.probes.startup (dict "targetPort" .Values.backend.service.targetPort )) | nindent 12 }} + {{- end }} + {{- with .Values.backend.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + {{- range $index, $value := .Values.mountFiles }} + - name: "files-{{ $index }}" + mountPath: {{ $value.path }} + subPath: content + {{- end }} + {{- range $name, $volume := .Values.backend.persistence }} + - name: "{{ $name }}" + mountPath: "{{ $volume.mountPath }}" + {{- end }} + {{- range .Values.backend.extraVolumeMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath | default "" }} + readOnly: {{ .readOnly }} + {{- end }} + {{- if .Values.backend.themeCustomization.enabled }} + - name: theme-customization + mountPath: {{ .Values.backend.themeCustomization.mount_path }} + readOnly: true + {{- end }} + {{- with .Values.backend.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.backend.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.backend.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + {{- range $index, $value := .Values.mountFiles }} + - name: "files-{{ $index }}" + configMap: + name: "{{ include "conversations.fullname" $ }}-files-{{ $index }}" + {{- end }} + {{- range $name, $volume := .Values.backend.persistence }} + - name: "{{ $name }}" + {{- if eq $volume.type "emptyDir" }} + emptyDir: {} + {{- else }} + persistentVolumeClaim: + claimName: "{{ $fullName }}-{{ $name }}" + {{- end }} + {{- end }} + {{- if .Values.backend.themeCustomization.enabled }} + - name: theme-customization + configMap: + name: docs-theme-customization + {{- end }} + {{- range .Values.backend.extraVolumes }} + - name: {{ .name }} + {{- if .existingClaim }} + persistentVolumeClaim: + claimName: {{ .existingClaim }} + {{- else if .hostPath }} + hostPath: + {{ toYaml .hostPath | nindent 12 }} + {{- else if .csi }} + csi: + {{- toYaml .csi | nindent 12 }} + {{- else if .configMap }} + configMap: + {{- toYaml .configMap | nindent 12 }} + {{- else if .emptyDir }} + emptyDir: + {{- toYaml .emptyDir | nindent 12 }} + {{- else }} + emptyDir: {} + {{- end }} + {{- end }} +--- +{{ if .Values.backend.pdb.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ $fullName }} + namespace: {{ .Release.Namespace | quote }} +spec: + maxUnavailable: 1 + selector: + matchLabels: + {{- include "conversations.common.selectorLabels" (list . $component) | nindent 6 }} +{{ end }} diff --git a/src/helm/conversations/templates/backend_job.yml b/src/helm/conversations/templates/backend_job.yml new file mode 100644 index 0000000..556f7f1 --- /dev/null +++ b/src/helm/conversations/templates/backend_job.yml @@ -0,0 +1,124 @@ +{{- if .Values.backend.job.command -}} +{{- $envVars := include "conversations.common.env" (list . .Values.backend) -}} +{{- $fullName := include "conversations.backend.fullname" . -}} +{{- $component := "backend" -}} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ $fullName }}-{{ .Values.backend.job.name | default "random" | replace "_" "-" }} + namespace: {{ .Release.Namespace | quote }} + annotations: + argocd.argoproj.io/sync-options: Replace=true,Force=true + {{- with .Values.backend.job.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + labels: + {{- include "conversations.common.labels" (list . $component) | nindent 4 }} +spec: + template: + metadata: + annotations: + {{- with .Values.backend.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "conversations.common.selectorLabels" (list . $component) | nindent 8 }} + spec: + {{- if $.Values.image.credentials }} + imagePullSecrets: + - name: {{ include "conversations.secret.dockerconfigjson.name" (dict "fullname" (include "conversations.fullname" .) "imageCredentials" $.Values.image.credentials) }} + {{- end}} + shareProcessNamespace: {{ .Values.backend.shareProcessNamespace }} + containers: + {{- with .Values.backend.sidecars }} + {{- toYaml . | nindent 8 }} + {{- end }} + - name: {{ .Chart.Name }} + image: "{{ (.Values.backend.image | default dict).repository | default .Values.image.repository }}:{{ (.Values.backend.image | default dict).tag | default .Values.image.tag }}" + imagePullPolicy: {{ (.Values.backend.image | default dict).pullPolicy | default .Values.image.pullPolicy }} + {{- with .Values.backend.job.command }} + command: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.backend.args }} + args: + {{- toYaml . | nindent 12 }} + {{- end }} + env: + {{- if $envVars}} + {{- $envVars | indent 12 }} + {{- end }} + {{- with .Values.backend.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.backend.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + {{- range $index, $value := .Values.mountFiles }} + - name: "files-{{ $index }}" + mountPath: {{ $value.path }} + subPath: content + {{- end }} + {{- range $name, $volume := .Values.backend.persistence }} + - name: "{{ $name }}" + mountPath: "{{ $volume.mountPath }}" + {{- end }} + {{- range .Values.backend.extraVolumeMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath | default "" }} + readOnly: {{ .readOnly }} + {{- end }} + {{- with .Values.backend.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.backend.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.backend.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + restartPolicy: {{ .Values.backend.job.restartPolicy }} + volumes: + {{- range $index, $value := .Values.mountFiles }} + - name: "files-{{ $index }}" + configMap: + name: "{{ include "conversations.fullname" $ }}-files-{{ $index }}" + {{- end }} + {{- range $name, $volume := .Values.backend.persistence }} + - name: "{{ $name }}" + {{- if eq $volume.type "emptyDir" }} + emptyDir: {} + {{- else }} + persistentVolumeClaim: + claimName: "{{ $fullName }}-{{ $name }}" + {{- end }} + {{- end }} + {{- range .Values.backend.extraVolumes }} + - name: {{ .name }} + {{- if .existingClaim }} + persistentVolumeClaim: + claimName: {{ .existingClaim }} + {{- else if .hostPath }} + hostPath: + {{ toYaml .hostPath | nindent 12 }} + {{- else if .csi }} + csi: + {{- toYaml .csi | nindent 12 }} + {{- else if .configMap }} + configMap: + {{- toYaml .configMap | nindent 12 }} + {{- else if .emptyDir }} + emptyDir: + {{- toYaml .emptyDir | nindent 12 }} + {{- else }} + emptyDir: {} + {{- end }} + {{- end }} +{{- end }} diff --git a/src/helm/conversations/templates/backend_job_createsuperuser.yaml b/src/helm/conversations/templates/backend_job_createsuperuser.yaml new file mode 100644 index 0000000..d0a73db --- /dev/null +++ b/src/helm/conversations/templates/backend_job_createsuperuser.yaml @@ -0,0 +1,124 @@ +{{- $envVars := include "conversations.common.env" (list . .Values.backend) -}} +{{- $fullName := include "conversations.backend.fullname" . -}} +{{- $component := "backend" -}} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ $fullName }}-createsuperuser + namespace: {{ .Release.Namespace | quote }} + annotations: + argocd.argoproj.io/sync-options: Replace=true,Force=true + {{- with .Values.backend.migrateJobAnnotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + labels: + {{- include "conversations.common.labels" (list . $component) | nindent 4 }} +spec: + ttlSecondsAfterFinished: {{ .Values.backend.jobs.ttlSecondsAfterFinished }} + backoffLimit: {{ .Values.backend.jobs.backoffLimit }} + template: + metadata: + annotations: + {{- with .Values.backend.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "conversations.common.selectorLabels" (list . $component) | nindent 8 }} + spec: + {{- if $.Values.image.credentials }} + imagePullSecrets: + - name: {{ include "conversations.secret.dockerconfigjson.name" (dict "fullname" (include "conversations.fullname" .) "imageCredentials" $.Values.image.credentials) }} + {{- end}} + shareProcessNamespace: {{ .Values.backend.shareProcessNamespace }} + containers: + {{- with .Values.backend.sidecars }} + {{- toYaml . | nindent 8 }} + {{- end }} + - name: {{ .Chart.Name }} + image: "{{ (.Values.backend.image | default dict).repository | default .Values.image.repository }}:{{ (.Values.backend.image | default dict).tag | default .Values.image.tag }}" + imagePullPolicy: {{ (.Values.backend.image | default dict).pullPolicy | default .Values.image.pullPolicy }} + {{- with .Values.backend.createsuperuser.command }} + command: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.backend.args }} + args: + {{- toYaml . | nindent 12 }} + {{- end }} + env: + {{- if $envVars}} + {{- $envVars | indent 12 }} + {{- end }} + {{- with .Values.backend.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.backend.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + {{- range $index, $value := .Values.mountFiles }} + - name: "files-{{ $index }}" + mountPath: {{ $value.path }} + subPath: content + {{- end }} + {{- range $name, $volume := .Values.backend.persistence }} + - name: "{{ $name }}" + mountPath: "{{ $volume.mountPath }}" + {{- end }} + {{- range .Values.backend.extraVolumeMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath | default "" }} + readOnly: {{ .readOnly }} + {{- end }} + {{- with .Values.backend.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.backend.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.backend.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + restartPolicy: {{ .Values.backend.createsuperuser.restartPolicy }} + volumes: + {{- range $index, $value := .Values.mountFiles }} + - name: "files-{{ $index }}" + configMap: + name: "{{ include "conversations.fullname" $ }}-files-{{ $index }}" + {{- end }} + {{- range $name, $volume := .Values.backend.persistence }} + - name: "{{ $name }}" + {{- if eq $volume.type "emptyDir" }} + emptyDir: {} + {{- else }} + persistentVolumeClaim: + claimName: "{{ $fullName }}-{{ $name }}" + {{- end }} + {{- end }} + {{- range .Values.backend.extraVolumes }} + - name: {{ .name }} + {{- if .existingClaim }} + persistentVolumeClaim: + claimName: {{ .existingClaim }} + {{- else if .hostPath }} + hostPath: + {{ toYaml .hostPath | nindent 12 }} + {{- else if .csi }} + csi: + {{- toYaml .csi | nindent 12 }} + {{- else if .configMap }} + configMap: + {{- toYaml .configMap | nindent 12 }} + {{- else if .emptyDir }} + emptyDir: + {{- toYaml .emptyDir | nindent 12 }} + {{- else }} + emptyDir: {} + {{- end }} + {{- end }} diff --git a/src/helm/conversations/templates/backend_job_migrate.yaml b/src/helm/conversations/templates/backend_job_migrate.yaml new file mode 100644 index 0000000..69a6ded --- /dev/null +++ b/src/helm/conversations/templates/backend_job_migrate.yaml @@ -0,0 +1,124 @@ +{{- $envVars := include "conversations.common.env" (list . .Values.backend) -}} +{{- $fullName := include "conversations.backend.fullname" . -}} +{{- $component := "backend" -}} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ $fullName }}-migrate + namespace: {{ .Release.Namespace | quote }} + annotations: + argocd.argoproj.io/sync-options: Replace=true,Force=true + {{- with .Values.backend.migrateJobAnnotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + labels: + {{- include "conversations.common.labels" (list . $component) | nindent 4 }} +spec: + ttlSecondsAfterFinished: {{ .Values.backend.jobs.ttlSecondsAfterFinished }} + backoffLimit: {{ .Values.backend.jobs.backoffLimit }} + template: + metadata: + annotations: + {{- with .Values.backend.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "conversations.common.selectorLabels" (list . $component) | nindent 8 }} + spec: + {{- if $.Values.image.credentials }} + imagePullSecrets: + - name: {{ include "conversations.secret.dockerconfigjson.name" (dict "fullname" (include "conversations.fullname" .) "imageCredentials" $.Values.image.credentials) }} + {{- end}} + shareProcessNamespace: {{ .Values.backend.shareProcessNamespace }} + containers: + {{- with .Values.backend.sidecars }} + {{- toYaml . | nindent 8 }} + {{- end }} + - name: {{ .Chart.Name }} + image: "{{ (.Values.backend.image | default dict).repository | default .Values.image.repository }}:{{ (.Values.backend.image | default dict).tag | default .Values.image.tag }}" + imagePullPolicy: {{ (.Values.backend.image | default dict).pullPolicy | default .Values.image.pullPolicy }} + {{- with .Values.backend.migrate.command }} + command: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.backend.args }} + args: + {{- toYaml . | nindent 12 }} + {{- end }} + env: + {{- if $envVars}} + {{- $envVars | indent 12 }} + {{- end }} + {{- with .Values.backend.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.backend.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + {{- range $index, $value := .Values.mountFiles }} + - name: "files-{{ $index }}" + mountPath: {{ $value.path }} + subPath: content + {{- end }} + {{- range $name, $volume := .Values.backend.persistence }} + - name: "{{ $name }}" + mountPath: "{{ $volume.mountPath }}" + {{- end }} + {{- range .Values.backend.extraVolumeMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath | default "" }} + readOnly: {{ .readOnly }} + {{- end }} + {{- with .Values.backend.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.backend.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.backend.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + restartPolicy: {{ .Values.backend.migrate.restartPolicy }} + volumes: + {{- range $index, $value := .Values.mountFiles }} + - name: "files-{{ $index }}" + configMap: + name: "{{ include "conversations.fullname" $ }}-files-{{ $index }}" + {{- end }} + {{- range $name, $volume := .Values.backend.persistence }} + - name: "{{ $name }}" + {{- if eq $volume.type "emptyDir" }} + emptyDir: {} + {{- else }} + persistentVolumeClaim: + claimName: "{{ $fullName }}-{{ $name }}" + {{- end }} + {{- end }} + {{- range .Values.backend.extraVolumes }} + - name: {{ .name }} + {{- if .existingClaim }} + persistentVolumeClaim: + claimName: {{ .existingClaim }} + {{- else if .hostPath }} + hostPath: + {{ toYaml .hostPath | nindent 12 }} + {{- else if .csi }} + csi: + {{- toYaml .csi | nindent 12 }} + {{- else if .configMap }} + configMap: + {{- toYaml .configMap | nindent 12 }} + {{- else if .emptyDir }} + emptyDir: + {{- toYaml .emptyDir | nindent 12 }} + {{- else }} + emptyDir: {} + {{- end }} + {{- end }} diff --git a/src/helm/conversations/templates/backend_svc.yaml b/src/helm/conversations/templates/backend_svc.yaml new file mode 100644 index 0000000..ec89e15 --- /dev/null +++ b/src/helm/conversations/templates/backend_svc.yaml @@ -0,0 +1,21 @@ +{{- $envVars := include "conversations.common.env" (list . .Values.backend) -}} +{{- $fullName := include "conversations.backend.fullname" . -}} +{{- $component := "backend" -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ $fullName }} + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "conversations.common.labels" (list . $component) | nindent 4 }} + annotations: + {{- toYaml $.Values.backend.service.annotations | nindent 4 }} +spec: + type: {{ .Values.backend.service.type }} + ports: + - port: {{ .Values.backend.service.port }} + targetPort: {{ .Values.backend.service.targetPort }} + protocol: TCP + name: http + selector: + {{- include "conversations.common.selectorLabels" (list . $component) | nindent 4 }} diff --git a/src/helm/conversations/templates/frontend_deployment.yaml b/src/helm/conversations/templates/frontend_deployment.yaml new file mode 100644 index 0000000..8eed362 --- /dev/null +++ b/src/helm/conversations/templates/frontend_deployment.yaml @@ -0,0 +1,153 @@ +{{- $envVars := include "conversations.common.env" (list . .Values.frontend) -}} +{{- $fullName := include "conversations.frontend.fullname" . -}} +{{- $component := "frontend" -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ $fullName }} + namespace: {{ .Release.Namespace | quote }} + annotations: + {{- with .Values.backend.dpAnnotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + labels: + {{- include "conversations.common.labels" (list . $component) | nindent 4 }} +spec: + replicas: {{ .Values.frontend.replicas }} + selector: + matchLabels: + {{- include "conversations.common.selectorLabels" (list . $component) | nindent 6 }} + template: + metadata: + annotations: + {{- with .Values.frontend.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "conversations.common.selectorLabels" (list . $component) | nindent 8 }} + spec: + {{- if $.Values.image.credentials }} + imagePullSecrets: + - name: {{ include "conversations.secret.dockerconfigjson.name" (dict "fullname" (include "conversations.fullname" .) "imageCredentials" $.Values.image.credentials) }} + {{- end}} + shareProcessNamespace: {{ .Values.frontend.shareProcessNamespace }} + containers: + {{- with .Values.frontend.sidecars }} + {{- toYaml . | nindent 8 }} + {{- end }} + - name: {{ .Chart.Name }} + image: "{{ (.Values.frontend.image | default dict).repository | default .Values.image.repository }}:{{ (.Values.frontend.image | default dict).tag | default .Values.image.tag }}" + imagePullPolicy: {{ (.Values.frontend.image | default dict).pullPolicy | default .Values.image.pullPolicy }} + {{- with .Values.frontend.command }} + command: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.frontend.args }} + args: + {{- toYaml . | nindent 12 }} + {{- end }} + env: + {{- if $envVars}} + {{- $envVars | indent 12 }} + {{- end }} + {{- with .Values.frontend.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: http + containerPort: {{ .Values.frontend.service.targetPort }} + protocol: TCP + {{- if .Values.frontend.probes.liveness }} + livenessProbe: + {{- include "conversations.probes.abstract" (merge .Values.frontend.probes.liveness (dict "targetPort" .Values.frontend.service.targetPort )) | nindent 12 }} + {{- end }} + {{- if .Values.frontend.probes.readiness }} + readinessProbe: + {{- include "conversations.probes.abstract" (merge .Values.frontend.probes.readiness (dict "targetPort" .Values.frontend.service.targetPort )) | nindent 12 }} + {{- end }} + {{- if .Values.frontend.probes.startup }} + startupProbe: + {{- include "conversations.probes.abstract" (merge .Values.frontend.probes.startup (dict "targetPort" .Values.frontend.service.targetPort )) | nindent 12 }} + {{- end }} + {{- with .Values.frontend.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + {{- range $index, $value := .Values.mountFiles }} + - name: "files-{{ $index }}" + mountPath: {{ $value.path }} + subPath: content + {{- end }} + {{- range $name, $volume := .Values.frontend.persistence }} + - name: "{{ $name }}" + mountPath: "{{ $volume.mountPath }}" + {{- end }} + {{- range .Values.frontend.extraVolumeMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath | default "" }} + readOnly: {{ .readOnly }} + {{- end }} + {{- with .Values.frontend.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.frontend.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.frontend.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + {{- range $index, $value := .Values.mountFiles }} + - name: "files-{{ $index }}" + configMap: + name: "{{ include "conversations.fullname" $ }}-files-{{ $index }}" + {{- end }} + {{- range $name, $volume := .Values.frontend.persistence }} + - name: "{{ $name }}" + {{- if eq $volume.type "emptyDir" }} + emptyDir: {} + {{- else }} + persistentVolumeClaim: + claimName: "{{ $fullName }}-{{ $name }}" + {{- end }} + {{- end }} + {{- range .Values.frontend.extraVolumes }} + - name: {{ .name }} + {{- if .existingClaim }} + persistentVolumeClaim: + claimName: {{ .existingClaim }} + {{- else if .hostPath }} + hostPath: + {{ toYaml .hostPath | nindent 12 }} + {{- else if .csi }} + csi: + {{- toYaml .csi | nindent 12 }} + {{- else if .configMap }} + configMap: + {{- toYaml .configMap | nindent 12 }} + {{- else if .emptyDir }} + emptyDir: + {{- toYaml .emptyDir | nindent 12 }} + {{- else }} + emptyDir: {} + {{- end }} + {{- end }} +--- +{{ if .Values.frontend.pdb.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ $fullName }} + namespace: {{ .Release.Namespace | quote }} +spec: + maxUnavailable: 1 + selector: + matchLabels: + {{- include "conversations.common.selectorLabels" (list . $component) | nindent 6 }} +{{ end }} diff --git a/src/helm/conversations/templates/frontend_svc.yaml b/src/helm/conversations/templates/frontend_svc.yaml new file mode 100644 index 0000000..8c1a6b6 --- /dev/null +++ b/src/helm/conversations/templates/frontend_svc.yaml @@ -0,0 +1,21 @@ +{{- $envVars := include "conversations.common.env" (list . .Values.frontend) -}} +{{- $fullName := include "conversations.frontend.fullname" . -}} +{{- $component := "frontend" -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ $fullName }} + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "conversations.common.labels" (list . $component) | nindent 4 }} + annotations: + {{- toYaml $.Values.frontend.service.annotations | nindent 4 }} +spec: + type: {{ .Values.frontend.service.type }} + ports: + - port: {{ .Values.frontend.service.port }} + targetPort: {{ .Values.frontend.service.targetPort }} + protocol: TCP + name: http + selector: + {{- include "conversations.common.selectorLabels" (list . $component) | nindent 4 }} diff --git a/src/helm/conversations/templates/ingress.yaml b/src/helm/conversations/templates/ingress.yaml new file mode 100644 index 0000000..e081050 --- /dev/null +++ b/src/helm/conversations/templates/ingress.yaml @@ -0,0 +1,117 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "conversations.fullname" . -}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "conversations.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls.enabled }} + tls: + {{- if .Values.ingress.host }} + - secretName: {{ .Values.ingress.tls.secretName | default (printf "%s-tls" $fullName) | quote }} + hosts: + - {{ .Values.ingress.host | quote }} + {{- end }} + {{- range .Values.ingress.tls.additional }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- if .Values.ingress.host }} + - host: {{ .Values.ingress.host | quote }} + http: + paths: + - path: {{ .Values.ingress.path | quote }} + {{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }} + pathType: Prefix + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ include "conversations.frontend.fullname" . }} + port: + number: {{ .Values.frontend.service.port }} + {{- else }} + serviceName: {{ include "conversations.frontend.fullname" . }} + servicePort: {{ .Values.frontend.service.port }} + {{- end }} + - path: /api + {{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }} + pathType: Prefix + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ include "conversations.backend.fullname" . }} + port: + number: {{ .Values.backend.service.port }} + {{- else }} + serviceName: {{ include "conversations.backend.fullname" . }} + servicePort: {{ .Values.backend.service.port }} + {{- end }} + {{- with .Values.ingress.customBackends }} + {{- toYaml . | nindent 10 }} + {{- end }} + {{- end }} + {{- range .Values.ingress.hosts }} + - host: {{ . | quote }} + http: + paths: + - path: {{ $.Values.ingress.path | quote }} + {{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }} + pathType: Prefix + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ include "conversations.frontend.fullname" $ }} + port: + number: {{ $.Values.frontend.service.port }} + {{- else }} + serviceName: {{ include "conversations.frontend.fullname" $ }} + servicePort: {{ $.Values.frontend.service.port }} + {{- end }} + - path: /api + {{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }} + pathType: Prefix + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ include "conversations.backend.fullname" $ }} + port: + number: {{ $.Values.backend.service.port }} + {{- else }} + serviceName: {{ include "conversations.backend.fullname" $ }} + servicePort: {{ $.Values.backend.service.port }} + {{- end }} + {{- with $.Values.ingress.customBackends }} + {{- toYaml . | nindent 10 }} + {{- end }} + {{- end }} +{{- end }} diff --git a/src/helm/conversations/templates/ingress_admin.yaml b/src/helm/conversations/templates/ingress_admin.yaml new file mode 100644 index 0000000..bdcaf11 --- /dev/null +++ b/src/helm/conversations/templates/ingress_admin.yaml @@ -0,0 +1,97 @@ +{{- if .Values.ingressAdmin.enabled -}} +{{- $fullName := include "conversations.fullname" . -}} +{{- if and .Values.ingressAdmin.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingressAdmin.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingressAdmin.annotations "kubernetes.io/ingress.class" .Values.ingressAdmin.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }}-admin + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "conversations.labels" . | nindent 4 }} + {{- with .Values.ingressAdmin.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingressAdmin.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingressAdmin.className }} + {{- end }} + {{- if .Values.ingressAdmin.tls.enabled }} + tls: + {{- if .Values.ingressAdmin.host }} + - secretName: {{ .Values.ingressAdmin.tls.secretName | default (printf "%s-tls" $fullName) | quote }} + hosts: + - {{ .Values.ingressAdmin.host | quote }} + {{- end }} + {{- range .Values.ingressAdmin.tls.additional }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- if .Values.ingressAdmin.host }} + - host: {{ .Values.ingressAdmin.host | quote }} + http: + paths: + - path: {{ .Values.ingressAdmin.path | quote }} + {{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }} + pathType: Prefix + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ include "conversations.backend.fullname" . }} + port: + number: {{ .Values.backend.service.port }} + {{- else }} + serviceName: {{ include "conversations.backend.fullname" . }} + servicePort: {{ .Values.backend.service.port }} + {{- end }} + - path: /static + {{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }} + pathType: Prefix + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ include "conversations.backend.fullname" . }} + port: + number: {{ .Values.backend.service.port }} + {{- else }} + serviceName: {{ include "conversations.backend.fullname" . }} + servicePort: {{ .Values.backend.service.port }} + {{- end }} + {{- end }} + {{- range .Values.ingressAdmin.hosts }} + - host: {{ . | quote }} + http: + paths: + - path: {{ $.Values.ingressAdmin.path | quote }} + {{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }} + pathType: Prefix + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ include "conversations.backend.fullname" $ }} + port: + number: {{ $.Values.backend.service.port }} + {{- else }} + serviceName: {{ include "conversations.backend.fullname" $ }} + servicePort: {{ $.Values.backend.service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/src/helm/conversations/templates/ingress_media.yaml b/src/helm/conversations/templates/ingress_media.yaml new file mode 100644 index 0000000..938dd1a --- /dev/null +++ b/src/helm/conversations/templates/ingress_media.yaml @@ -0,0 +1,83 @@ +{{- if .Values.ingressMedia.enabled -}} +{{- $fullName := include "conversations.fullname" . -}} +{{- if and .Values.ingressMedia.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingressMedia.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingressMedia.annotations "kubernetes.io/ingress.class" .Values.ingressMedia.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }}-media + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "conversations.labels" . | nindent 4 }} + {{- with .Values.ingressMedia.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingressMedia.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingressMedia.className }} + {{- end }} + {{- if .Values.ingressMedia.tls.enabled }} + tls: + {{- if .Values.ingressMedia.host }} + - secretName: {{ .Values.ingressMedia.tls.secretName | default (printf "%s-tls" $fullName) | quote }} + hosts: + - {{ .Values.ingressMedia.host | quote }} + {{- end }} + {{- range .Values.ingressMedia.tls.additional }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- if .Values.ingressMedia.host }} + - host: {{ .Values.ingressMedia.host | quote }} + http: + paths: + - path: {{ .Values.ingressMedia.path | quote }} + {{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }} + pathType: ImplementationSpecific + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }}-media + port: + number: {{ .Values.serviceMedia.port }} + {{- else }} + serviceName: {{ $fullName }}-media + servicePort: {{ .Values.serviceMedia.port }} + {{- end }} + {{- end }} + {{- range .Values.ingressMedia.hosts }} + - host: {{ . | quote }} + http: + paths: + - path: {{ $.Values.ingressMedia.path | quote }} + {{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }} + pathType: ImplementationSpecific + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }}-media + port: + number: {{ .Values.serviceMedia.port }} + {{- else }} + serviceName: {{ $fullName }}-media + servicePort: {{ .Values.serviceMedia.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/src/helm/conversations/templates/ingress_posthog.yaml b/src/helm/conversations/templates/ingress_posthog.yaml new file mode 100644 index 0000000..db82ba2 --- /dev/null +++ b/src/helm/conversations/templates/ingress_posthog.yaml @@ -0,0 +1,86 @@ +{{- if .Values.posthog.ingress.enabled -}} +{{- $fullName := include "conversations.fullname" . -}} +{{- if and .Values.posthog.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.posthog.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.posthog.ingress.annotations "kubernetes.io/ingress.class" .Values.posthog.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }}-posthog + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "conversations.labels" . | nindent 4 }} + {{- with .Values.posthog.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.posthog.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.posthog.ingress.className }} + {{- end }} + {{- if .Values.posthog.ingress.tls.enabled }} + tls: + {{- if .Values.posthog.ingress.host }} + - secretName: {{ $fullName }}-posthog-tls + hosts: + - {{ .Values.posthog.ingress.host | quote }} + {{- end }} + {{- range .Values.posthog.ingress.tls.additional }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- if .Values.posthog.ingress.host }} + - host: {{ .Values.posthog.ingress.host | quote }} + http: + paths: + - path: {{ .Values.posthog.ingress.path }} + {{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }} + pathType: Prefix + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ include "conversations.posthog.fullname" . }}-proxy + port: + number: {{ .Values.posthog.service.port }} + {{- else }} + serviceName: {{ include "conversations.posthog.fullname" . }}-proxy + servicePort: {{ .Values.posthog.service.port }} + {{- end }} + {{- end }} + {{- range .Values.posthog.ingress.hosts }} + - host: {{ . | quote }} + http: + paths: + - path: {{ $.Values.posthog.ingress.path | quote }} + {{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }} + pathType: Prefix + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ include "conversations.posthog.fullname" . }}-proxy + port: + number: {{ $.Values.posthog.service.port }} + {{- else }} + serviceName: {{ include "conversations.posthog.fullname" . }}-proxy + servicePort: {{ $.Values.posthog.service.port }} + {{- end }} + {{- with $.Values.posthog.service.customBackends }} + {{- toYaml . | nindent 10 }} + {{- end }} + {{- end }} +{{- end }} diff --git a/src/helm/conversations/templates/ingress_posthog_assets.yaml b/src/helm/conversations/templates/ingress_posthog_assets.yaml new file mode 100644 index 0000000..549381e --- /dev/null +++ b/src/helm/conversations/templates/ingress_posthog_assets.yaml @@ -0,0 +1,66 @@ +{{- if .Values.posthog.ingressAssets.enabled -}} +{{- $fullName := include "conversations.fullname" . -}} +{{- if and .Values.posthog.ingressAssets.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.posthog.ingressAssets.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.posthog.ingressAssets.annotations "kubernetes.io/ingress.class" .Values.posthog.ingressAssets.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }}-posthog-assets + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "conversations.labels" . | nindent 4 }} + {{- with .Values.posthog.ingressAssets.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.posthog.ingressAssets.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.posthog.ingressAssets.className }} + {{- end }} + {{- if .Values.posthog.ingressAssets.tls.enabled }} + tls: + {{- if .Values.posthog.ingressAssets.host }} + - secretName: {{ $fullName }}-posthog-tls + hosts: + - {{ .Values.posthog.ingressAssets.host | quote }} + {{- end }} + {{- range .Values.posthog.ingressAssets.tls.additional }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- if .Values.posthog.ingressAssets.host }} + - host: {{ .Values.posthog.ingressAssets.host | quote }} + http: + paths: + {{- range .Values.posthog.ingressAssets.paths }} + - path: {{ . | quote }} + {{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }} + pathType: Prefix + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ include "conversations.posthog.fullname" $ }}-assets-proxy + port: + number: {{ $.Values.posthog.assetsService.port }} + {{- else }} + serviceName: {{ include "conversations.posthog.fullname" $ }}-assets-proxy + servicePort: {{ $.Values.posthog.assetsService.port }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/src/helm/conversations/templates/media_svc.yaml b/src/helm/conversations/templates/media_svc.yaml new file mode 100644 index 0000000..66f3058 --- /dev/null +++ b/src/helm/conversations/templates/media_svc.yaml @@ -0,0 +1,14 @@ +{{- $fullName := include "conversations.fullname" . -}} +{{- $component := "media" -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ $fullName }}-media + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "conversations.common.labels" (list . $component) | nindent 4 }} + annotations: + {{- toYaml $.Values.serviceMedia.annotations | nindent 4 }} +spec: + type: ExternalName + externalName: {{ $.Values.serviceMedia.host }} diff --git a/src/helm/conversations/templates/posthog_assets_svc.yaml b/src/helm/conversations/templates/posthog_assets_svc.yaml new file mode 100644 index 0000000..c760cea --- /dev/null +++ b/src/helm/conversations/templates/posthog_assets_svc.yaml @@ -0,0 +1,24 @@ +{{- if .Values.posthog.ingressAssets.enabled -}} +{{- $envVars := include "conversations.common.env" (list . .Values.posthog) -}} +{{- $fullName := include "conversations.posthog.fullname" . -}} +{{- $component := "posthog" -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ $fullName }}-assets-proxy + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "conversations.common.labels" (list . $component) | nindent 4 }} + annotations: + {{- toYaml $.Values.posthog.assetsService.annotations | nindent 4 }} +spec: + type: {{ .Values.posthog.assetsService.type }} + externalName: {{ .Values.posthog.assetsService.externalName }} + ports: + - port: {{ .Values.posthog.assetsService.port }} + targetPort: {{ .Values.posthog.assetsService.targetPort }} + protocol: TCP + name: https + selector: + {{- include "conversations.common.selectorLabels" (list . $component) | nindent 4 }} +{{- end }} diff --git a/src/helm/conversations/templates/posthog_svc.yaml b/src/helm/conversations/templates/posthog_svc.yaml new file mode 100644 index 0000000..766660f --- /dev/null +++ b/src/helm/conversations/templates/posthog_svc.yaml @@ -0,0 +1,24 @@ +{{- if .Values.posthog.ingress.enabled -}} +{{- $envVars := include "conversations.common.env" (list . .Values.posthog) -}} +{{- $fullName := include "conversations.posthog.fullname" . -}} +{{- $component := "posthog" -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ $fullName }}-proxy + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "conversations.common.labels" (list . $component) | nindent 4 }} + annotations: + {{- toYaml $.Values.posthog.service.annotations | nindent 4 }} +spec: + type: {{ .Values.posthog.service.type }} + externalName: {{ .Values.posthog.service.externalName }} + ports: + - port: {{ .Values.posthog.service.port }} + targetPort: {{ .Values.posthog.service.targetPort }} + protocol: TCP + name: https + selector: + {{- include "conversations.common.selectorLabels" (list . $component) | nindent 4 }} +{{- end }} diff --git a/src/helm/conversations/templates/theme_customization_file_cm.yaml b/src/helm/conversations/templates/theme_customization_file_cm.yaml new file mode 100644 index 0000000..7786963 --- /dev/null +++ b/src/helm/conversations/templates/theme_customization_file_cm.yaml @@ -0,0 +1,10 @@ +{{- if .Values.backend.themeCustomization.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: docs-theme-customization + namespace: {{ .Release.Namespace }} +data: + default.json: | + {{ .Values.backend.themeCustomization.file_content | toJson | indent 4 }} +{{- end }} \ No newline at end of file diff --git a/src/helm/conversations/values.yaml b/src/helm/conversations/values.yaml new file mode 100644 index 0000000..cf21a22 --- /dev/null +++ b/src/helm/conversations/values.yaml @@ -0,0 +1,445 @@ +# Default values for conversations. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +## @section General configuration + +## @param image.repository Repository to use to pull conversations's container image +## @param image.tag conversations's container tag +## @param image.pullPolicy Container image pull policy +## @extra image.credentials.username Username for container registry authentication +## @extra image.credentials.password Password for container registry authentication +## @extra image.credentials.registry Registry url for which the credentials are specified +## @extra image.credentials.name Name of the generated secret for imagePullSecrets +image: + repository: lasuite/conversations-backend + pullPolicy: IfNotPresent + tag: "latest" + +## @param nameOverride Override the chart name +## @param fullnameOverride Override the full application name +nameOverride: "" +fullnameOverride: "" + +## @skip commonEnvVars +commonEnvVars: &commonEnvVars + <<: [] + +## @param ingress.enabled whether to enable the Ingress or not +## @param ingress.className IngressClass to use for the Ingress +## @param ingress.host Host for the Ingress +## @param ingress.path Path to use for the Ingress +ingress: + enabled: false + className: null + host: conversations.example.com + path: / + ## @param ingress.hosts Additional host to configure for the Ingress + hosts: [] + # - chart-example.local + ## @param ingress.tls.enabled Weather to enable TLS for the Ingress + ## @param ingress.tls.secretName Secret name for TLS config + ## @skip ingress.tls.additional + ## @extra ingress.tls.additional[].secretName Secret name for additional TLS config + ## @extra ingress.tls.additional[].hosts[] Hosts for additional TLS config + tls: + enabled: true + secretName: null + additional: [] + + ## @param ingress.customBackends Add custom backends to ingress + customBackends: [] + +## @param ingressAdmin.enabled whether to enable the Ingress or not +## @param ingressAdmin.className IngressClass to use for the Ingress +## @param ingressAdmin.host Host for the Ingress +## @param ingressAdmin.path Path to use for the Ingress +ingressAdmin: + enabled: false + className: null + host: conversations.example.com + path: /admin + ## @param ingressAdmin.hosts Additional host to configure for the Ingress + hosts: [ ] + # - chart-example.local + ## @param ingressAdmin.tls.enabled Weather to enable TLS for the Ingress + ## @param ingressAdmin.tls.secretName Secret name for TLS config + ## @skip ingressAdmin.tls.additional + ## @extra ingressAdmin.tls.additional[].secretName Secret name for additional TLS config + ## @extra ingressAdmin.tls.additional[].hosts[] Hosts for additional TLS config + tls: + enabled: true + secretName: null + additional: [] + +## @param ingressMedia.enabled whether to enable the Ingress or not +## @param ingressMedia.className IngressClass to use for the Ingress +## @param ingressMedia.host Host for the Ingress +## @param ingressMedia.path Path to use for the Ingress +ingressMedia: + enabled: false + className: null + host: conversations.example.com + path: /media/(.*) + ## @param ingressMedia.hosts Additional host to configure for the Ingress + hosts: [ ] + # - chart-example.local + ## @param ingressMedia.tls.enabled Weather to enable TLS for the Ingress + ## @param ingressMedia.tls.secretName Secret name for TLS config + ## @skip ingressMedia.tls.additional + ## @extra ingressMedia.tls.additional[].secretName Secret name for additional TLS config + ## @extra ingressMedia.tls.additional[].hosts[] Hosts for additional TLS config + tls: + enabled: true + secretName: null + additional: [] + + ## @param ingressMedia.annotations.nginx.ingress.kubernetes.io/auth-url + ## @param ingressMedia.annotations.nginx.ingress.kubernetes.io/auth-response-headers + ## @param ingressMedia.annotations.nginx.ingress.kubernetes.io/upstream-vhost + ## @param ingressMedia.annotations.nginx.ingress.kubernetes.io/configuration-snippet + annotations: + nginx.ingress.kubernetes.io/auth-url: https://conversations.example.com/api/v1.0/documents/media-auth/ + nginx.ingress.kubernetes.io/auth-response-headers: "Authorization, X-Amz-Date, X-Amz-Content-SHA256" + nginx.ingress.kubernetes.io/upstream-vhost: minio.conversations.svc.cluster.local:9000 + nginx.ingress.kubernetes.io/configuration-snippet: | + add_header Content-Security-Policy "default-src 'none'" always; + +## @param serviceMedia.host +## @param serviceMedia.port +## @param serviceMedia.annotations +serviceMedia: + host: minio.conversations.svc.cluster.local + port: 9000 + annotations: {} + + +## @section backend + +backend: + + ## @param backend.command Override the backend container command + command: [] + + ## @param backend.args Override the backend container args + args: [] + + ## @param backend.replicas Amount of backend replicas + replicas: 3 + + ## @param backend.shareProcessNamespace Enable share process namespace between containers + shareProcessNamespace: false + + ## @param backend.sidecars Add sidecars containers to backend deployment + sidecars: [] + + ## @param backend.migrateJobAnnotations Annotations for the migrate job + migrateJobAnnotations: {} + + ## @param backend.jobs.ttlSecondsAfterFinished Period to wait before remove jobs + ## @param backend.jobs.backoffLimit Numbers of jobs retries + jobs: + ttlSecondsAfterFinished: 30 + backoffLimit: 2 + + ## @param backend.securityContext Configure backend Pod security context + securityContext: null + + ## @param backend.envVars Configure backend container environment variables + ## @extra backend.envVars.BY_VALUE Example environment variable by setting value directly + ## @extra backend.envVars.FROM_CONFIGMAP.configMapKeyRef.name Name of a ConfigMap when configuring env vars from a ConfigMap + ## @extra backend.envVars.FROM_CONFIGMAP.configMapKeyRef.key Key within a ConfigMap when configuring env vars from a ConfigMap + ## @extra backend.envVars.FROM_SECRET.secretKeyRef.name Name of a Secret when configuring env vars from a Secret + ## @extra backend.envVars.FROM_SECRET.secretKeyRef.key Key within a Secret when configuring env vars from a Secret + ## @skip backend.envVars + envVars: + <<: *commonEnvVars + + ## @param backend.podAnnotations Annotations to add to the backend Pod + podAnnotations: {} + + ## @param backend.dpAnnotations Annotations to add to the backend Deployment + dpAnnotations: {} + + ## @param backend.service.type backend Service type + ## @param backend.service.port backend Service listening port + ## @param backend.service.targetPort backend container listening port + ## @param backend.service.annotations Annotations to add to the backend Service + service: + type: ClusterIP + port: 80 + targetPort: 8000 + annotations: {} + + ## @param backend.migrate.command backend migrate command + ## @param backend.migrate.restartPolicy backend migrate job restart policy + migrate: + command: + - /bin/sh + - "-c" + - | + while ! python manage.py check --database default > /dev/null 2>&1 + do + echo "Database not ready" + sleep 2 + done + + echo "Database is ready" + + python manage.py migrate --no-input + restartPolicy: Never + + ## @param backend.createsuperuser.command backend migrate command + ## @param backend.createsuperuser.restartPolicy backend migrate job restart policy + createsuperuser: + command: + - "/bin/sh" + - "-c" + - | + while ! python manage.py check --database default > /dev/null 2>&1 + do + echo "Database not ready" + sleep 2 + done + + echo "Database is ready" + + python manage.py createsuperuser --email $DJANGO_SUPERUSER_EMAIL --password $DJANGO_SUPERUSER_PASSWORD + restartPolicy: Never + + ## @extra backend.job job dedicated to run a random management command, for example after a deployment + ## @param backend.job.name The name to use to describe this job + ## @param backend.job.command The management command to execute + ## @param backend.job.restartPolicy The restart policy for the job. + ## @extra backend.job.annotations Annotations to add to the job [default: argocd.argoproj.io/hook: PostSync] + ## @skip backend.job.annotations.argocd.argoproj.io/hook + job: + name: "" + command: [] + restartPolicy: Never + annotations: + argocd.argoproj.io/hook: PostSync + + ## @param backend.probes.liveness.path [nullable] Configure path for backend HTTP liveness probe + ## @param backend.probes.liveness.targetPort [nullable] Configure port for backend HTTP liveness probe + ## @param backend.probes.liveness.initialDelaySeconds [nullable] Configure initial delay for backend liveness probe + ## @param backend.probes.liveness.initialDelaySeconds [nullable] Configure timeout for backend liveness probe + ## @param backend.probes.startup.path [nullable] Configure path for backend HTTP startup probe + ## @param backend.probes.startup.targetPort [nullable] Configure port for backend HTTP startup probe + ## @param backend.probes.startup.initialDelaySeconds [nullable] Configure initial delay for backend startup probe + ## @param backend.probes.startup.initialDelaySeconds [nullable] Configure timeout for backend startup probe + ## @param backend.probes.readiness.path [nullable] Configure path for backend HTTP readiness probe + ## @param backend.probes.readiness.targetPort [nullable] Configure port for backend HTTP readiness probe + ## @param backend.probes.readiness.initialDelaySeconds [nullable] Configure initial delay for backend readiness probe + ## @param backend.probes.readiness.initialDelaySeconds [nullable] Configure timeout for backend readiness probe + probes: + liveness: + path: /__heartbeat__ + initialDelaySeconds: 10 + readiness: + path: /__lbheartbeat__ + initialDelaySeconds: 10 + + ## @param backend.resources Resource requirements for the backend container + resources: {} + + ## @param backend.nodeSelector Node selector for the backend Pod + nodeSelector: {} + + ## @param backend.tolerations Tolerations for the backend Pod + tolerations: [] + + ## @param backend.affinity Affinity for the backend Pod + affinity: {} + + ## @param backend.persistence Additional volumes to create and mount on the backend. Used for debugging purposes + ## @extra backend.persistence.volume-name.size Size of the additional volume + ## @extra backend.persistence.volume-name.type Type of the additional volume, persistentVolumeClaim or emptyDir + ## @extra backend.persistence.volume-name.mountPath Path where the volume should be mounted to + persistence: {} + + ## @param backend.extraVolumeMounts Additional volumes to mount on the backend. + extraVolumeMounts: [] + + ## @param backend.extraVolumes Additional volumes to mount on the backend. + extraVolumes: [] + + ## @param backend.pdb.enabled Enable pdb on backend + pdb: + enabled: true + + ## @param backend.themeCustomization.enabled Enable theme customization + ## @param backend.themeCustomization.file_content Content of the theme customization file. Must be a json object. + ## @param backend.themeCustomization.mount_path Path where the customization file will be mounted in the backend deployment. + themeCustomization: + enabled: false + file_content: "" + mount_path: /app/conversations/configuration/theme + +## @section frontend + +frontend: + ## @param frontend.image.repository Repository to use to pull conversations's frontend container image + ## @param frontend.image.tag conversations's frontend container tag + ## @param frontend.image.pullPolicy frontend container image pull policy + image: + repository: lasuite/conversations-frontend + pullPolicy: IfNotPresent + tag: "latest" + + ## @param frontend.command Override the frontend container command + command: [] + + ## @param frontend.args Override the frontend container args + args: [] + + ## @param frontend.replicas Amount of frontend replicas + replicas: 3 + + ## @param frontend.shareProcessNamespace Enable share process namefrontend between containers + shareProcessNamespace: false + + ## @param frontend.sidecars Add sidecars containers to frontend deployment + sidecars: [] + + ## @param frontend.securityContext Configure frontend Pod security context + securityContext: null + + ## @param frontend.envVars Configure frontend container environment variables + ## @extra frontend.envVars.BY_VALUE Example environment variable by setting value directly + ## @extra frontend.envVars.FROM_CONFIGMAP.configMapKeyRef.name Name of a ConfigMap when configuring env vars from a ConfigMap + ## @extra frontend.envVars.FROM_CONFIGMAP.configMapKeyRef.key Key within a ConfigMap when configuring env vars from a ConfigMap + ## @extra frontend.envVars.FROM_SECRET.secretKeyRef.name Name of a Secret when configuring env vars from a Secret + ## @extra frontend.envVars.FROM_SECRET.secretKeyRef.key Key within a Secret when configuring env vars from a Secret + ## @skip frontend.envVars + envVars: + <<: *commonEnvVars + + ## @param frontend.podAnnotations Annotations to add to the frontend Pod + podAnnotations: {} + + ## @param frontend.dpAnnotations Annotations to add to the frontend Deployment + dpAnnotations: {} + + ## @param frontend.service.type frontend Service type + ## @param frontend.service.port frontend Service listening port + ## @param frontend.service.targetPort frontend container listening port + ## @param frontend.service.annotations Annotations to add to the frontend Service + service: + type: ClusterIP + port: 80 + targetPort: 8080 + annotations: {} + + ## @param frontend.probes Configure probe for frontend + ## @extra frontend.probes.liveness.path Configure path for frontend HTTP liveness probe + ## @extra frontend.probes.liveness.targetPort Configure port for frontend HTTP liveness probe + ## @extra frontend.probes.liveness.initialDelaySeconds Configure initial delay for frontend liveness probe + ## @extra frontend.probes.liveness.initialDelaySeconds Configure timeout for frontend liveness probe + ## @extra frontend.probes.startup.path Configure path for frontend HTTP startup probe + ## @extra frontend.probes.startup.targetPort Configure port for frontend HTTP startup probe + ## @extra frontend.probes.startup.initialDelaySeconds Configure initial delay for frontend startup probe + ## @extra frontend.probes.startup.initialDelaySeconds Configure timeout for frontend startup probe + ## @extra frontend.probes.readiness.path Configure path for frontend HTTP readiness probe + ## @extra frontend.probes.readiness.targetPort Configure port for frontend HTTP readiness probe + ## @extra frontend.probes.readiness.initialDelaySeconds Configure initial delay for frontend readiness probe + ## @extra frontend.probes.readiness.initialDelaySeconds Configure timeout for frontend readiness probe + probes: {} + + ## @param frontend.resources Resource requirements for the frontend container + resources: {} + + ## @param frontend.nodeSelector Node selector for the frontend Pod + nodeSelector: {} + + ## @param frontend.tolerations Tolerations for the frontend Pod + tolerations: [] + + ## @param frontend.affinity Affinity for the frontend Pod + affinity: {} + + ## @param frontend.persistence Additional volumes to create and mount on the frontend. Used for debugging purposes + ## @extra frontend.persistence.volume-name.size Size of the additional volume + ## @extra frontend.persistence.volume-name.type Type of the additional volume, persistentVolumeClaim or emptyDir + ## @extra frontend.persistence.volume-name.mountPath Path where the volume should be mounted to + persistence: {} + + ## @param frontend.extraVolumeMounts Additional volumes to mount on the frontend. + extraVolumeMounts: [] + + ## @param frontend.extraVolumes Additional volumes to mount on the frontend. + extraVolumes: [] + + ## @param frontend.pdb.enabled Enable pdb on frontend + pdb: + enabled: true + +## @section posthog + +posthog: + + ## @param posthog.ingress.enabled Enable or disable the ingress resource creation + ## @param posthog.ingress.className Kubernetes ingress class name to use (e.g., nginx, traefik) + ## @param posthog.ingress.host Primary hostname for the ingress resource + ## @param posthog.ingress.path URL path prefix for the ingress routes (e.g., /) + ## @param posthog.ingress.hosts Additional hostnames array to be included in the ingress + ## @param posthog.ingress.tls.enabled Enable or disable TLS/HTTPS for the ingress + ## @param posthog.ingress.tls.additional Additional TLS configurations for extra hosts/certificates + ## @param posthog.ingress.customBackends Custom backend service configurations for the ingress + ## @param posthog.ingress.annotations Additional Kubernetes annotations to apply to the ingress + ingress: + enabled: false + className: null + host: conversations.example.com + path: / + hosts: [ ] + tls: + enabled: true + additional: [ ] + + customBackends: [ ] + annotations: {} + + ## @param posthog.ingressAssets.enabled Enable or disable the ingress resource creation + ## @param posthog.ingressAssets.className Kubernetes ingress class name to use (e.g., nginx, traefik) + ## @param posthog.ingressAssets.host Primary hostname for the ingress resource + ## @param posthog.ingressAssets.paths URL paths prefix for the ingress routes (e.g., /static) + ## @param posthog.ingressAssets.hosts Additional hostnames array to be included in the ingress + ## @param posthog.ingressAssets.tls.enabled Enable or disable TLS/HTTPS for the ingress + ## @param posthog.ingressAssets.tls.additional Additional TLS configurations for extra hosts/certificates + ## @param posthog.ingressAssets.customBackends Custom backend service configurations for the ingress + ## @param posthog.ingressAssets.annotations Additional Kubernetes annotations to apply to the ingress + ingressAssets: + enabled: false + className: null + host: conversations.example.com + paths: + - /static + - /array + hosts: [ ] + tls: + enabled: true + additional: [ ] + + customBackends: [ ] + annotations: {} + + ## @param posthog.service.type Service type (e.g. ExternalName, ClusterIP, LoadBalancer) + ## @param posthog.service.externalName External service hostname when type is ExternalName + ## @param posthog.service.port Port number for the service + ## @param posthog.service.annotations Additional annotations to apply to the service + service: + type: ExternalName + externalName: eu.i.posthog.com + port: 443 + annotations: {} + + ## @param posthog.assetsService.type Service type (e.g. ExternalName, ClusterIP, LoadBalancer) + ## @param posthog.assetsService.externalName External service hostname when type is ExternalName + ## @param posthog.assetsService.port Port number for the service + ## @param posthog.assetsService.annotations Additional annotations to apply to the service + assetsService: + type: ExternalName + externalName: eu-assets.i.posthog.com + port: 443 + annotations: {} diff --git a/src/helm/env.d/dev-staging/values.conversations.yaml.gotmpl b/src/helm/env.d/dev-staging/values.conversations.yaml.gotmpl new file mode 100644 index 0000000..f32b46f --- /dev/null +++ b/src/helm/env.d/dev-staging/values.conversations.yaml.gotmpl @@ -0,0 +1,148 @@ +djangoSecretKey: &djangoSecretKey "lkjsdlfkjsldkfjslkdfjslkdjfslkdjf" +djangoSuperUserEmail: admin@example.com +djangoSuperUserPass: admin +aiApiKey: changeme +aiBaseUrl: changeme + +image: + repository: localhost:5001/conversations-backend + pullPolicy: Always + tag: "latest" + +backend: + replicas: 1 + envVars: + COLLABORATION_SERVER_SECRET: my-secret + DJANGO_CSRF_TRUSTED_ORIGINS: https://conversations.127.0.0.1.nip.io + DJANGO_CONFIGURATION: Feature + DJANGO_ALLOWED_HOSTS: conversations.127.0.0.1.nip.io + DJANGO_SERVER_TO_SERVER_API_TOKENS: secret-api-key + DJANGO_SECRET_KEY: *djangoSecretKey + DJANGO_SETTINGS_MODULE: conversations.settings + DJANGO_SUPERUSER_PASSWORD: admin + DJANGO_EMAIL_BRAND_NAME: "La Suite Numรฉrique" + DJANGO_EMAIL_HOST: "maildev" + DJANGO_EMAIL_LOGO_IMG: https://conversations.127.0.0.1.nip.io/assets/logo-suite-numerique.png + DJANGO_EMAIL_PORT: 1025 + DJANGO_EMAIL_USE_SSL: False + LOGGING_LEVEL_HANDLERS_CONSOLE: ERROR + LOGGING_LEVEL_LOGGERS_ROOT: INFO + LOGGING_LEVEL_LOGGERS_APP: INFO + OIDC_USERINFO_SHORTNAME_FIELD: "given_name" + OIDC_USERINFO_FULLNAME_FIELDS: "given_name,usual_name" + OIDC_OP_JWKS_ENDPOINT: https://fca.integ01.dev-agentconnect.fr/api/v2/jwks + OIDC_OP_AUTHORIZATION_ENDPOINT: https://fca.integ01.dev-agentconnect.fr/api/v2/authorize + OIDC_OP_TOKEN_ENDPOINT: https://fca.integ01.dev-agentconnect.fr/api/v2/token + OIDC_OP_USER_ENDPOINT: https://fca.integ01.dev-agentconnect.fr/api/v2/userinfo + OIDC_OP_LOGOUT_ENDPOINT: https://fca.integ01.dev-agentconnect.fr/api/v2/session/end + OIDC_RP_CLIENT_ID: 29eeb7c9-38e3-4e4f-8b39-7f7f1fec9efb + OIDC_RP_CLIENT_SECRET: 98a736e58775584a1051479703d5e0966d3b782e2a49b0068534f149f943c85f + OIDC_RP_SIGN_ALGO: RS256 + OIDC_RP_SCOPES: "openid email given_name usual_name groups" + OIDC_STORE_REFRESH_TOKEN_KEY: qnw7gZrOFLkLuZIixzuxksNORFJyjWyi5ACugNchKJY= + OIDC_STORE_ACCESS_TOKEN: True + OIDC_STORE_REFRESH_TOKEN: True + OIDC_USERINFO_SHORTNAME_FIELD: "given_name" + OIDC_USERINFO_FULLNAME_FIELDS: "given_name,usual_name" + LOGIN_REDIRECT_URL: https://conversations.127.0.0.1.nip.io + LOGIN_REDIRECT_URL_FAILURE: https://conversations.127.0.0.1.nip.io + LOGOUT_REDIRECT_URL: https://conversations.127.0.0.1.nip.io + DB_HOST: postgres-postgresql + DB_NAME: conversations + DB_USER: dinum + DB_PASSWORD: pass + DB_PORT: 5432 + POSTGRES_DB: conversations + POSTGRES_USER: dinum + POSTGRES_PASSWORD: pass + REDIS_URL: redis://default:pass@redis-master:6379/2 + AWS_S3_ENDPOINT_URL: http://minio.conversations.svc.cluster.local:9000 + AWS_S3_ACCESS_KEY_ID: root + AWS_S3_SECRET_ACCESS_KEY: password + AWS_STORAGE_BUCKET_NAME: conversations-media-storage + STORAGES_STATICFILES_BACKEND: django.contrib.staticfiles.storage.StaticFilesStorage + OIDC_STORE_ACCESS_TOKEN: True + OIDC_STORE_REFRESH_TOKEN: True + OIDC_STORE_REFRESH_TOKEN_KEY: qnw7gZrOFLkLuZIixzuxksNORFJyjWyi5ACugNchKJY= + migrate: + command: + - "/bin/sh" + - "-c" + - | + python manage.py migrate --no-input && + python manage.py create_demo --force + restartPolicy: Never + + command: + - "gunicorn" + - "-c" + - "/usr/local/etc/gunicorn/conversations.py" + - "conversations.wsgi:application" + - "--reload" + + createsuperuser: + command: + - "/bin/sh" + - "-c" + - | + python manage.py createsuperuser --email admin@example.com --password admin + restartPolicy: Never + + # Extra volume mounts to manage our local custom CA and avoid to set ssl_verify: false + extraVolumeMounts: + - name: certs + mountPath: /usr/local/lib/python3.12/site-packages/certifi/cacert.pem + subPath: cacert.pem + + # Extra volumes to manage our local custom CA and avoid to set ssl_verify: false + extraVolumes: + - name: certs + configMap: + name: certifi + items: + - key: cacert.pem + path: cacert.pem + +frontend: + envVars: + PORT: 8080 + NEXT_PUBLIC_API_ORIGIN: https://conversations.127.0.0.1.nip.io + + replicas: 1 + command: + - yarn + - dev + + image: + repository: localhost:5001/conversations-frontend + pullPolicy: Always + tag: "latest" + +ingress: + enabled: true + host: conversations.127.0.0.1.nip.io + +ingressAdmin: + enabled: true + host: conversations.127.0.0.1.nip.io + +posthog: + ingress: + enabled: false + + ingressAssets: + enabled: false + +ingressMedia: + enabled: true + host: conversations.127.0.0.1.nip.io + + annotations: + nginx.ingress.kubernetes.io/auth-url: https://conversations.127.0.0.1.nip.io/api/v1.0/documents/media-auth/ + nginx.ingress.kubernetes.io/auth-response-headers: "Authorization, X-Amz-Date, X-Amz-Content-SHA256" + nginx.ingress.kubernetes.io/upstream-vhost: minio.conversations.svc.cluster.local:9000 + nginx.ingress.kubernetes.io/rewrite-target: /conversations-media-storage/$1 + +serviceMedia: + host: minio.conversations.svc.cluster.local + port: 9000 diff --git a/src/helm/env.d/dev/configuration/theme/demo.json b/src/helm/env.d/dev/configuration/theme/demo.json new file mode 100644 index 0000000..983e465 --- /dev/null +++ b/src/helm/env.d/dev/configuration/theme/demo.json @@ -0,0 +1,129 @@ +{ + "translations": { + "en": { + "translation": { + "Docs": "MyDocs", + "New doc": "+" + } + } + }, + "footer": { + "default": { + "logo": { + "src": "/assets/icon-docs.svg", + "width": "54px", + "alt": "Docs Logo", + "withTitle": true + }, + "externalLinks": [ + { + "label": "Github", + "href": "https://github.com/suitenumerique/conversations/" + }, + { + "label": "DINUM", + "href": "https://www.numerique.gouv.fr/dinum/" + } + ], + "bottomInformation": { + "label": "Unless otherwise stated, all content on this site is under", + "link": { + "label": "licence etalab-2.0", + "href": "https://github.com/etalab/licence-ouverte/blob/master/LO.md" + } + } + }, + "en": { + "legalLinks": [ + { + "label": "Legal Notice", + "href": "#" + }, + { + "label": "Personal data and cookies", + "href": "#" + }, + { + "label": "Accessibility", + "href": "#" + } + ], + "bottomInformation": { + "label": "Unless otherwise stated, all content on this site is under", + "link": { + "label": "licence MIT", + "href": "https://github.com/suitenumerique/conversations/blob/main/LICENSE" + } + } + }, + "fr": { + "legalLinks": [ + { + "label": "Mentions lรฉgales", + "href": "#" + }, + { + "label": "Donnรฉes personnelles et cookies", + "href": "#" + }, + { + "label": "Accessibilitรฉ", + "href": "#" + } + ], + "bottomInformation": { + "label": "Sauf mention contraire, tout le contenu de ce site est sous", + "link": { + "label": "licence MIT", + "href": "https://github.com/suitenumerique/conversations/blob/main/LICENSE" + } + } + }, + "de": { + "legalLinks": [ + { + "label": "Impressum", + "href": "#" + }, + { + "label": "Personenbezogene Daten und Cookies", + "href": "#" + }, + { + "label": "Barrierefreiheit", + "href": "#" + } + ], + "bottomInformation": { + "label": "Sofern nicht anders angegeben, steht der gesamte Inhalt dieser Website unter", + "link": { + "label": "licence MIT", + "href": "https://github.com/suitenumerique/conversations/blob/main/LICENSE" + } + } + }, + "nl": { + "legalLinks": [ + { + "label": "Wettelijke bepalingen", + "href": "#" + }, + { + "label": "Persoonlijke gegevens en cookies", + "href": "#" + }, + { + "label": "Toegankelijkheid", + "href": "#" + } + ], + "bottomInformation": { + "label": "Tenzij anders vermeld, is alle inhoud van deze site ondergebracht onder", + "link": { + "label": "licence MIT", + "href": "https://github.com/suitenumerique/conversations/blob/main/LICENSE" + } + } + } + } +} diff --git a/src/helm/env.d/dev/values.conversations.yaml.gotmpl b/src/helm/env.d/dev/values.conversations.yaml.gotmpl new file mode 100644 index 0000000..6a6d814 --- /dev/null +++ b/src/helm/env.d/dev/values.conversations.yaml.gotmpl @@ -0,0 +1,163 @@ +djangoSecretKey: &djangoSecretKey "lkjsdlfkjsldkfjslkdfjslkdjfslkdjf" +djangoSuperUserEmail: admin@example.com +djangoSuperUserPass: admin +aiApiKey: changeme +aiBaseUrl: changeme +oidc: + clientId: conversations + clientSecret: ThisIsAnExampleKeyForDevPurposeOnly + +image: + repository: localhost:5001/conversations-backend + pullPolicy: Always + tag: "latest" + +backend: + replicas: 1 + envVars: + COLLABORATION_SERVER_SECRET: my-secret + DJANGO_CSRF_TRUSTED_ORIGINS: https://conversations.127.0.0.1.nip.io + DJANGO_CONFIGURATION: Feature + DJANGO_ALLOWED_HOSTS: conversations.127.0.0.1.nip.io + DJANGO_SERVER_TO_SERVER_API_TOKENS: secret-api-key + DJANGO_SECRET_KEY: *djangoSecretKey + DJANGO_SETTINGS_MODULE: conversations.settings + DJANGO_SUPERUSER_PASSWORD: admin + DJANGO_EMAIL_BRAND_NAME: "La Suite Numรฉrique" + DJANGO_EMAIL_HOST: "maildev" + DJANGO_EMAIL_LOGO_IMG: https://conversations.127.0.0.1.nip.io/assets/logo-suite-numerique.png + DJANGO_EMAIL_PORT: 1025 + DJANGO_EMAIL_USE_SSL: False + LOGGING_LEVEL_HANDLERS_CONSOLE: ERROR + LOGGING_LEVEL_LOGGERS_ROOT: INFO + LOGGING_LEVEL_LOGGERS_APP: INFO + OIDC_USERINFO_SHORTNAME_FIELD: "given_name" + OIDC_USERINFO_FULLNAME_FIELDS: "given_name,usual_name" + OIDC_OP_JWKS_ENDPOINT: https://docs-keycloak.127.0.0.1.nip.io/realms/conversations/protocol/openid-connect/certs + OIDC_OP_AUTHORIZATION_ENDPOINT: https://docs-keycloak.127.0.0.1.nip.io/realms/conversations/protocol/openid-connect/auth + OIDC_OP_TOKEN_ENDPOINT: https://docs-keycloak.127.0.0.1.nip.io/realms/conversations/protocol/openid-connect/token + OIDC_OP_USER_ENDPOINT: https://docs-keycloak.127.0.0.1.nip.io/realms/conversations/protocol/openid-connect/userinfo + OIDC_OP_LOGOUT_ENDPOINT: https://docs-keycloak.127.0.0.1.nip.io/realms/conversations/protocol/openid-connect/logout + OIDC_RP_CLIENT_ID: conversations + OIDC_RP_CLIENT_SECRET: ThisIsAnExampleKeyForDevPurposeOnly + OIDC_RP_SIGN_ALGO: RS256 + OIDC_RP_SCOPES: "openid email" + LOGIN_REDIRECT_URL: https://conversations.127.0.0.1.nip.io + LOGIN_REDIRECT_URL_FAILURE: https://conversations.127.0.0.1.nip.io + LOGOUT_REDIRECT_URL: https://conversations.127.0.0.1.nip.io + DB_HOST: postgres-postgresql + DB_NAME: conversations + DB_USER: dinum + DB_PASSWORD: pass + DB_PORT: 5432 + POSTGRES_DB: conversations + POSTGRES_USER: dinum + POSTGRES_PASSWORD: pass + REDIS_URL: redis://default:pass@redis-master:6379/1 + DJANGO_CELERY_BROKER_URL: redis://default:pass@redis-master:6379/1 + AWS_S3_ENDPOINT_URL: http://minio.conversations.svc.cluster.local:9000 + AWS_S3_ACCESS_KEY_ID: root + AWS_S3_SECRET_ACCESS_KEY: password + AWS_STORAGE_BUCKET_NAME: conversations-media-storage + STORAGES_STATICFILES_BACKEND: django.contrib.staticfiles.storage.StaticFilesStorage + CACHES_KEY_PREFIX: "{{ now | unixEpoch }}" + migrate: + command: + - "/bin/sh" + - "-c" + - | + while ! python manage.py check --database default > /dev/null 2>&1 + do + echo "Database not ready" + sleep 2 + done + + echo "Database is ready" + + python manage.py migrate --no-input && + python manage.py create_demo --force + restartPolicy: Never + + command: + - "gunicorn" + - "-c" + - "/usr/local/etc/gunicorn/conversations.py" + - "conversations.wsgi:application" + - "--reload" + + createsuperuser: + command: + - "/bin/sh" + - "-c" + - | + while ! python manage.py check --database default > /dev/null 2>&1 + do + echo "Database not ready" + sleep 2 + done + + echo "Database is ready" + python manage.py createsuperuser --email admin@example.com --password admin + restartPolicy: Never + + themeCustomization: + enabled: true + file_content: {{ readFile "./configuration/theme/demo.json" }} + + # Extra volume mounts to manage our local custom CA and avoid to set ssl_verify: false + extraVolumeMounts: + - name: certs + mountPath: /usr/local/lib/python3.13/site-packages/certifi/cacert.pem + subPath: cacert.pem + + # Extra volumes to manage our local custom CA and avoid to set ssl_verify: false + extraVolumes: + - name: certs + configMap: + name: certifi + items: + - key: cacert.pem + path: cacert.pem +frontend: + envVars: + PORT: 8080 + NEXT_PUBLIC_API_ORIGIN: https://conversations.127.0.0.1.nip.io + + replicas: 1 + command: + - yarn + - dev + + image: + repository: localhost:5001/conversations-frontend + pullPolicy: Always + tag: "latest" + +ingress: + enabled: true + host: conversations.127.0.0.1.nip.io + +ingressAdmin: + enabled: true + host: conversations.127.0.0.1.nip.io + +posthog: + ingress: + enabled: false + + ingressAssets: + enabled: false + +ingressMedia: + enabled: true + host: conversations.127.0.0.1.nip.io + + annotations: + nginx.ingress.kubernetes.io/auth-url: https://conversations.127.0.0.1.nip.io/api/v1.0/documents/media-auth/ + nginx.ingress.kubernetes.io/auth-response-headers: "Authorization, X-Amz-Date, X-Amz-Content-SHA256" + nginx.ingress.kubernetes.io/upstream-vhost: minio.conversations.svc.cluster.local:9000 + nginx.ingress.kubernetes.io/rewrite-target: /conversations-media-storage/$1 + +serviceMedia: + host: minio.conversations.svc.cluster.local + port: 9000 diff --git a/src/helm/helmfile.yaml b/src/helm/helmfile.yaml new file mode 100644 index 0000000..2aa933d --- /dev/null +++ b/src/helm/helmfile.yaml @@ -0,0 +1,98 @@ +environments: + dev: + values: + - version: 0.0.1 +--- +repositories: +- name: bitnami + url: registry-1.docker.io/bitnamicharts + oci: true +--- + +releases: + - name: keycloak + installed: {{ eq .Environment.Name "dev" | toYaml }} + missingFileHandler: Warn + namespace: {{ .Namespace }} + chart: bitnami/keycloak + version: 17.3.6 + values: + - postgresql: + auth: + username: keycloak + password: keycloak + database: keycloak + - extraEnvVars: + - name: KEYCLOAK_EXTRA_ARGS + value: "--import-realm" + - name: KC_HOSTNAME_URL + value: https://conversations-keycloak.127.0.0.1.nip.io + - extraVolumes: + - name: import + configMap: + name: conversations-keycloak + - extraVolumeMounts: + - name: import + mountPath: /opt/bitnami/keycloak/data/import/ + - auth: + adminUser: su + adminPassword: su + - proxy: edge + - ingress: + enabled: true + hostname: conversations-keycloak.127.0.0.1.nip.io + - extraDeploy: + - apiVersion: v1 + kind: ConfigMap + metadata: + name: conversations-keycloak + namespace: {{ .Namespace }} + data: + conversations.json: | +{{ readFile "../../docker/auth/realm.json" | replace "http://localhost:3200" "https://conversations.127.0.0.1.nip.io" | indent 14 }} + + - name: postgres + installed: {{ eq .Environment.Name "dev" | toYaml }} + namespace: {{ .Namespace }} + chart: bitnami/postgresql + version: 13.1.5 + values: + - auth: + username: dinum + password: pass + database: conversations + - tls: + enabled: true + autoGenerated: true + + - name: minio + installed: {{ eq .Environment.Name "dev" | toYaml }} + namespace: {{ .Namespace }} + chart: bitnami/minio + version: 12.10.10 + values: + - auth: + rootUser: root + rootPassword: password + - provisioning: + enabled: true + buckets: + - name: conversations-media-storage + versioning: true + + - name: redis + installed: {{ eq .Environment.Name "dev" | toYaml }} + namespace: {{ .Namespace }} + chart: bitnami/redis + version: 20.6.2 + values: + - auth: + password: pass + architecture: standalone + + - name: conversations + version: {{ .Values.version }} + namespace: {{ .Namespace }} + chart: ./conversations + values: + - env.d/{{ .Environment.Name }}/values.conversations.yaml.gotmpl \ No newline at end of file diff --git a/src/mail/bin/html-to-plain-text b/src/mail/bin/html-to-plain-text new file mode 100755 index 0000000..ced0c13 --- /dev/null +++ b/src/mail/bin/html-to-plain-text @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -eo pipefail +# Run html-to-text to convert all html files to text files +DIR_MAILS="../backend/core/templates/mail/" + +if [ ! -d "${DIR_MAILS}" ]; then + mkdir -p "${DIR_MAILS}"; +fi + +if [ ! -d "${DIR_MAILS}"html/ ]; then + mkdir -p "${DIR_MAILS}"html/; + exit; +fi + +for file in "${DIR_MAILS}"html/*.html; + do html-to-text -j ./html-to-text.config.json < "$file" > "${file%.html}".txt; done; + +if [ ! -d "${DIR_MAILS}"text/ ]; then + mkdir -p "${DIR_MAILS}"text/; +fi + +mv "${DIR_MAILS}"html/*.txt "${DIR_MAILS}"text/; diff --git a/src/mail/bin/mjml-to-html b/src/mail/bin/mjml-to-html new file mode 100755 index 0000000..fb5710b --- /dev/null +++ b/src/mail/bin/mjml-to-html @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +# Run mjml command to convert all mjml templates to html files +DIR_MAILS="../backend/core/templates/mail/html/" + +if [ ! -d "${DIR_MAILS}" ]; then + mkdir -p "${DIR_MAILS}"; +fi +mjml mjml/*.mjml -o "${DIR_MAILS}"; diff --git a/src/mail/html-to-text.config.json b/src/mail/html-to-text.config.json new file mode 100644 index 0000000..8d2e57a --- /dev/null +++ b/src/mail/html-to-text.config.json @@ -0,0 +1,11 @@ +{ + "wordwrap": 600, + "selectors": [ + { + "selector": "h1", + "options": { + "uppercase": false + } + } + ] +} diff --git a/src/mail/mjml/invitation.mjml b/src/mail/mjml/invitation.mjml new file mode 100644 index 0000000..93fa89c --- /dev/null +++ b/src/mail/mjml/invitation.mjml @@ -0,0 +1,59 @@ +<mjml> + <mj-include path="./partial/header.mjml" /> + + <mj-body mj-class="bg--blue-100"> + <mj-wrapper css-class="wrapper" padding="5px 25px 0px 25px"> + <mj-section css-class="wrapper-logo"> + <mj-column> + <mj-image + align="center" + src="{{logo_img}}" + width="320px" + align="left" + alt="{%trans 'Logo email' %}" + /> + </mj-column> + </mj-section> + <mj-section mj-class="bg--white-100" padding="0px 20px 60px 20px"> + <mj-column> + <mj-text align="center"> + <h1>{{title|capfirst}}</h1> + </mj-text> + <!-- Main Message --> + <mj-text> + {{message|capfirst}} + <a href="{{link}}">{{document_title}}</a> + </mj-text> + <mj-button + href="{{link}}" + background-color="#000091" + color="white" + padding-bottom="30px" + > + {% trans "Open"%} + </mj-button> + <mj-divider + border-width="1px" + border-style="solid" + border-color="#DDDDDD" + width="30%" + align="center" + /> + <mj-text> + {% blocktrans %} + Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. + {% endblocktrans %} + </mj-text> + <!-- Signature --> + <mj-text> + <p> + {% blocktrans %} + Brought to you by {{brandname}} + {% endblocktrans %} + </p> + </mj-text> + </mj-column> + </mj-section> + </mj-wrapper> + </mj-body> +</mjml> diff --git a/src/mail/mjml/partial/header.mjml b/src/mail/mjml/partial/header.mjml new file mode 100644 index 0000000..c370dd0 --- /dev/null +++ b/src/mail/mjml/partial/header.mjml @@ -0,0 +1,49 @@ +<mj-head> + <mj-title>{{ title }}</mj-title> + <mj-preview> + <!-- + We load django tags here, in this way there are put within the body in html output + so the html-to-text command includes it within its output + --> + {% load i18n static extra_tags %} + {{ title }} + </mj-preview> + <mj-attributes> + <mj-font name="Roboto" href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700;900&display=swap" /> + <mj-all + font-family="Roboto, -apple-system, BlinkMacSystemFont, 'Segoe UI', Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif" + font-size="16px" + line-height="normal" + color="#3A3A3A" + /> + </mj-attributes> + <mj-style> + /* Reset */ + h1, h2, h3, h4, h5, h6, p { + margin: 0; + padding: 0; + line-height: normal; + } + + a { + color: #000091; + } + </mj-style> + <mj-style> + /* Global styles */ + h1 { + color: #161616; + font-size: 1.4rem; + } + + .wrapper { + background: #FFFFFF; + border-radius: 0 0 6px 6px; + box-shadow: 0 0 6px rgba(2 117 180 / 0.3); + } + + .wrapper-logo td{ + padding: 0!important; + } + </mj-style> +</mj-head> diff --git a/src/mail/package.json b/src/mail/package.json new file mode 100644 index 0000000..0fe7556 --- /dev/null +++ b/src/mail/package.json @@ -0,0 +1,22 @@ +{ + "name": "mail_mjml", + "version": "0.0.1", + "description": "An util to generate html and text django's templates from mjml templates", + "type": "module", + "dependencies": { + "@html-to/text-cli": "0.5.4", + "mjml": "4.15.3" + }, + "private": true, + "scripts": { + "build-mjml-to-html": "bash ./bin/mjml-to-html", + "build-html-to-plain-text": "bash ./bin/html-to-plain-text", + "build": "yarn build-mjml-to-html && yarn build-html-to-plain-text" + }, + "volta": { + "node": "22" + }, + "repository": "https://github.com/numerique-gouv/conversations", + "author": "DINUM", + "license": "MIT" +} diff --git a/src/mail/yarn.lock b/src/mail/yarn.lock new file mode 100644 index 0000000..9771ab0 --- /dev/null +++ b/src/mail/yarn.lock @@ -0,0 +1,1236 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/runtime@^7.23.9": + version "7.26.10" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.10.tgz#a07b4d8fa27af131a633d7b3524db803eb4764c2" + integrity sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw== + dependencies: + regenerator-runtime "^0.14.0" + +"@html-to/text-cli@0.5.4": + version "0.5.4" + resolved "https://registry.yarnpkg.com/@html-to/text-cli/-/text-cli-0.5.4.tgz#f40804d139e0eaa43c923c3d845cbf1f8b133acf" + integrity sha512-V7WDfiYjXcibHGD6q61oW8HD68UPvBVkKit0X+9v54nTmLe8KDCc+56STleqqP7CzuEK5f/1jqa652fnr9Pmsw== + dependencies: + "@selderee/plugin-htmlparser2" "^0.11.0" + aspargvs "^0.6.0" + deepmerge "^4.3.1" + htmlparser2 "^8.0.2" + selderee "^0.11.0" + +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + +"@one-ini/wasm@0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@one-ini/wasm/-/wasm-0.1.1.tgz#6013659736c9dbfccc96e8a9c2b3de317df39323" + integrity sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw== + +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + +"@selderee/plugin-htmlparser2@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz#d5b5e29a7ba6d3958a1972c7be16f4b2c188c517" + integrity sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ== + dependencies: + domhandler "^5.0.3" + selderee "^0.11.0" + +abbrev@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-2.0.0.tgz#cf59829b8b4f03f89dda2771cb7f3653828c89bf" + integrity sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ== + +ansi-colors@^4.1.1: + version "4.1.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== + +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.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" + integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== + +ansi-styles@^4.0.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" + +aspargvs@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/aspargvs/-/aspargvs-0.6.0.tgz#15991c35425b044cb99868b6b3cfa7e051a28424" + integrity sha512-yUrWCd1hkK5UtDOne1gM3O+FoTFGQ+BVlSd4G7FczBz8+JaFn1uzvQzROxwp9hmlhIUtwSwyRuV9mHgd/WbXxg== + dependencies: + peberminta "^0.8.0" + +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== + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +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== + +brace-expansion@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7" + integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== + dependencies: + balanced-match "^1.0.0" + +braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +camel-case@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73" + integrity sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w== + dependencies: + no-case "^2.2.0" + upper-case "^1.1.1" + +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.0.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + 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" + +clean-css@^4.2.1: + version "4.2.4" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.4.tgz#733bf46eba4e607c6891ea57c24a989356831178" + integrity sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A== + dependencies: + source-map "~0.6.0" + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +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.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== + +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.19.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@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== + +config-chain@^1.1.13: + 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" + +cross-spawn@^7.0.0: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^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-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== + +deepmerge@^4.3.1: + 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== + +detect-node@2.1.0, 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== + +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@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-3.3.0.tgz#6db7ea46e4617eb15cf875df68b2b8524ce0037a" + integrity sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA== + dependencies: + domelementtype "^2.0.1" + +domhandler@^4.2.0: + 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.4.2: + 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, domutils@^3.1.0: + 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" + +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== + +editorconfig@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-1.0.4.tgz#040c9a8e9a6c5288388b87c2db07028aa89f53a3" + integrity sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q== + dependencies: + "@one-ini/wasm" "0.1.1" + commander "^10.0.0" + minimatch "9.0.1" + semver "^7.5.3" + +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== + +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, entities@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + +escalade@^3.1.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-goat@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-3.0.0.tgz#e8b5fb658553fe8a3c4959c316c6ebb8c842b19c" + integrity sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw== + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +foreground-child@^3.1.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.0.tgz#0ac8644c06e431439f8561db8ecf29a7b5519c77" + integrity sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" + +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +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@^10.3.10, glob@^10.3.3: + version "10.4.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" + integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + +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== + +html-minifier@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-4.0.0.tgz#cca9aad8bce1175e02e17a8c33e46d8988889f56" + integrity sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig== + dependencies: + camel-case "^3.0.0" + clean-css "^4.2.1" + commander "^2.19.0" + he "^1.2.0" + param-case "^2.1.1" + relateurl "^0.2.7" + uglify-js "^3.5.1" + +htmlparser2@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-5.0.1.tgz#7daa6fc3e35d6107ac95a4fc08781f091664f6e7" + integrity sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ== + dependencies: + domelementtype "^2.0.1" + domhandler "^3.3.0" + domutils "^2.4.2" + entities "^2.0.0" + +htmlparser2@^8.0.1, htmlparser2@^8.0.2: + 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" + +htmlparser2@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-9.1.0.tgz#cdb498d8a75a51f739b61d3f718136c369bc8c23" + integrity sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.1.0" + entities "^4.5.0" + +ini@^1.3.4: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +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-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.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-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== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +jackspeak@^3.1.2: + version "3.4.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + +js-beautify@^1.6.14: + version "1.15.1" + resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.15.1.tgz#4695afb508c324e1084ee0b952a102023fc65b64" + integrity sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA== + dependencies: + config-chain "^1.1.13" + editorconfig "^1.0.4" + glob "^10.3.3" + js-cookie "^3.0.5" + nopt "^7.2.0" + +js-cookie@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.5.tgz#0b7e2fd0c01552c58ba86e0841f94dc2557dcdbc" + integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw== + +juice@^10.0.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/juice/-/juice-10.0.1.tgz#a1492091ef739e4771b9f60aad1a608b5a8ea3ba" + integrity sha512-ZhJT1soxJCkOiO55/mz8yeBKTAJhRzX9WBO+16ZTqNTONnnVlUPyVBIzQ7lDRjaBdTbid+bAnyIon/GM3yp4cA== + dependencies: + cheerio "1.0.0-rc.12" + commander "^6.1.0" + mensch "^0.3.4" + slick "^1.12.2" + web-resource-inliner "^6.0.1" + +leac@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/leac/-/leac-0.6.0.tgz#dcf136e382e666bd2475f44a1096061b70dc0912" + integrity sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg== + +lodash@^4.17.15, lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +lower-case@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" + integrity sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA== + +lru-cache@^10.2.0: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + +mensch@^0.3.4: + version "0.3.4" + resolved "https://registry.yarnpkg.com/mensch/-/mensch-0.3.4.tgz#770f91b46cb16ea5b204ee735768c3f0c491fecd" + integrity sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g== + +mime@^2.4.6: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" + integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== + +minimatch@9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.1.tgz#8a555f541cf976c622daf078bb28f29fb927c253" + integrity sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^9.0.3, minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + +mjml-accordion@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-accordion/-/mjml-accordion-4.15.3.tgz#10e4c4297df3ad8dfa709fc64e887f89bfbff0a8" + integrity sha512-LPNVSj1LyUVYT9G1gWwSw3GSuDzDsQCu0tPB2uDsq4VesYNnU6v3iLCQidMiR6azmIt13OEozG700ygAUuA6Ng== + dependencies: + "@babel/runtime" "^7.23.9" + lodash "^4.17.21" + mjml-core "4.15.3" + +mjml-body@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-body/-/mjml-body-4.15.3.tgz#8885b2921f6daa1a287e8aea0924ee1fc4aaf222" + integrity sha512-7pfUOVPtmb0wC+oUOn4xBsAw4eT5DyD6xqaxj/kssu6RrFXOXgJaVnDPAI9AzIvXJ/5as9QrqRGYAddehwWpHQ== + dependencies: + "@babel/runtime" "^7.23.9" + lodash "^4.17.21" + mjml-core "4.15.3" + +mjml-button@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-button/-/mjml-button-4.15.3.tgz#34baf2d7fbf77a5febe6993e311103723279adbd" + integrity sha512-79qwn9AgdGjJR1vLnrcm2rq2AsAZkKC5JPwffTMG+Nja6zGYpTDZFZ56ekHWr/r1b5WxkukcPj2PdevUug8c+Q== + dependencies: + "@babel/runtime" "^7.23.9" + lodash "^4.17.21" + mjml-core "4.15.3" + +mjml-carousel@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-carousel/-/mjml-carousel-4.15.3.tgz#fe82d2c4c8020ef14f3b360316c670f7da294193" + integrity sha512-3ju6I4l7uUhPRrJfN3yK9AMsfHvrYbRkcJ1GRphFHzUj37B2J6qJOQUpzA547Y4aeh69TSb7HFVf1t12ejQxVw== + dependencies: + "@babel/runtime" "^7.23.9" + lodash "^4.17.21" + mjml-core "4.15.3" + +mjml-cli@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-cli/-/mjml-cli-4.15.3.tgz#5638f1919c952d224f51970a2fbf3141dee6d487" + integrity sha512-+V2TDw3tXUVEptFvLSerz125C2ogYl8klIBRY1m5BHd4JvGVf3yhx8N3PngByCzA6PGcv/eydGQN+wy34SHf0Q== + dependencies: + "@babel/runtime" "^7.23.9" + chokidar "^3.0.0" + glob "^10.3.10" + html-minifier "^4.0.0" + js-beautify "^1.6.14" + lodash "^4.17.21" + minimatch "^9.0.3" + mjml-core "4.15.3" + mjml-migrate "4.15.3" + mjml-parser-xml "4.15.3" + mjml-validator "4.15.3" + yargs "^17.7.2" + +mjml-column@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-column/-/mjml-column-4.15.3.tgz#ffc538f6b87a7340697f88600330110a40f82c05" + integrity sha512-hYdEFdJGHPbZJSEysykrevEbB07yhJGSwfDZEYDSbhQQFjV2tXrEgYcFD5EneMaowjb55e3divSJxU4c5q4Qgw== + dependencies: + "@babel/runtime" "^7.23.9" + lodash "^4.17.21" + mjml-core "4.15.3" + +mjml-core@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-core/-/mjml-core-4.15.3.tgz#96c30f49340b95bb9c825a6479557cc9ad1af6c6" + integrity sha512-Dmwk+2cgSD9L9GmTbEUNd8QxkTZtW9P7FN/ROZW/fGZD6Hq6/4TB0zEspg2Ow9eYjZXO2ofOJ3PaQEEShKV0kQ== + dependencies: + "@babel/runtime" "^7.23.9" + cheerio "1.0.0-rc.12" + detect-node "^2.0.4" + html-minifier "^4.0.0" + js-beautify "^1.6.14" + juice "^10.0.0" + lodash "^4.17.21" + mjml-migrate "4.15.3" + mjml-parser-xml "4.15.3" + mjml-validator "4.15.3" + +mjml-divider@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-divider/-/mjml-divider-4.15.3.tgz#2aadaf7e9955a9d9473f7093598f933aa289c683" + integrity sha512-vh27LQ9FG/01y0b9ntfqm+GT5AjJnDSDY9hilss2ixIUh0FemvfGRfsGVeV5UBVPBKK7Ffhvfqc7Rciob9Spzw== + dependencies: + "@babel/runtime" "^7.23.9" + lodash "^4.17.21" + mjml-core "4.15.3" + +mjml-group@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-group/-/mjml-group-4.15.3.tgz#7e4418d7d4b5d5d5e4d6af9865c25d6d358a7f75" + integrity sha512-HSu/rKnGZVKFq3ciT46vi1EOy+9mkB0HewO4+P6dP/Y0UerWkN6S3UK11Cxsj0cAp0vFwkPDCdOeEzRdpFEkzA== + dependencies: + "@babel/runtime" "^7.23.9" + lodash "^4.17.21" + mjml-core "4.15.3" + +mjml-head-attributes@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-head-attributes/-/mjml-head-attributes-4.15.3.tgz#4c81e561982fca2657bf3dda7576fcafec778b66" + integrity sha512-2ISo0r5ZKwkrvJgDou9xVPxxtXMaETe2AsAA02L89LnbB2KC0N5myNsHV0sEysTw9+CfCmgjAb0GAI5QGpxKkQ== + dependencies: + "@babel/runtime" "^7.23.9" + lodash "^4.17.21" + mjml-core "4.15.3" + +mjml-head-breakpoint@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-head-breakpoint/-/mjml-head-breakpoint-4.15.3.tgz#be1fbe6b4f6cd77f7f666b2cb9e48e81f727b74f" + integrity sha512-Eo56FA5C2v6ucmWQL/JBJ2z641pLOom4k0wP6CMZI2utfyiJ+e2Uuinj1KTrgDcEvW4EtU9HrfAqLK9UosLZlg== + dependencies: + "@babel/runtime" "^7.23.9" + lodash "^4.17.21" + mjml-core "4.15.3" + +mjml-head-font@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-head-font/-/mjml-head-font-4.15.3.tgz#0340872d0ffe9e29044d66ede452575cb7da3ddf" + integrity sha512-CzV2aDPpiNIIgGPHNcBhgyedKY4SX3BJoTwOobSwZVIlEA6TAWB4Z9WwFUmQqZOgo1AkkiTHPZQvGcEhFFXH6g== + dependencies: + "@babel/runtime" "^7.23.9" + lodash "^4.17.21" + mjml-core "4.15.3" + +mjml-head-html-attributes@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-head-html-attributes/-/mjml-head-html-attributes-4.15.3.tgz#852710724b976fac7aabd648f5f9770bfa1e21e5" + integrity sha512-MDNDPMBOgXUZYdxhosyrA2kudiGO8aogT0/cODyi2Ed9o/1S7W+je11JUYskQbncqhWKGxNyaP4VWa+6+vUC/g== + dependencies: + "@babel/runtime" "^7.23.9" + lodash "^4.17.21" + mjml-core "4.15.3" + +mjml-head-preview@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-head-preview/-/mjml-head-preview-4.15.3.tgz#710ce159974bf2924edb7f920dd05280a433afd3" + integrity sha512-J2PxCefUVeFwsAExhrKo4lwxDevc5aKj888HBl/wN4EuWOoOg06iOGCxz4Omd8dqyFsrqvbBuPqRzQ+VycGmaA== + dependencies: + "@babel/runtime" "^7.23.9" + lodash "^4.17.21" + mjml-core "4.15.3" + +mjml-head-style@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-head-style/-/mjml-head-style-4.15.3.tgz#66a9a3926888681578c2550c7444e4f8cbddfda3" + integrity sha512-9J+JuH+mKrQU65CaJ4KZegACUgNIlYmWQYx3VOBR/tyz+8kDYX7xBhKJCjQ1I4wj2Tvga3bykd89Oc2kFZ5WOw== + dependencies: + "@babel/runtime" "^7.23.9" + lodash "^4.17.21" + mjml-core "4.15.3" + +mjml-head-title@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-head-title/-/mjml-head-title-4.15.3.tgz#ccbd11a7771965f5ac5f3069f6c4f74668c9e6ea" + integrity sha512-IM59xRtsxID4DubQ0iLmoCGXguEe+9BFG4z6y2xQDrscIa4QY3KlfqgKGT69ojW+AVbXXJPEVqrAi4/eCsLItQ== + dependencies: + "@babel/runtime" "^7.23.9" + lodash "^4.17.21" + mjml-core "4.15.3" + +mjml-head@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-head/-/mjml-head-4.15.3.tgz#3e7311af0de4911dd167c877cf04d4291206cd2f" + integrity sha512-o3mRuuP/MB5fZycjD3KH/uXsnaPl7Oo8GtdbJTKtH1+O/3pz8GzGMkscTKa97l03DAG2EhGrzzLcU2A6eshwFw== + dependencies: + "@babel/runtime" "^7.23.9" + lodash "^4.17.21" + mjml-core "4.15.3" + +mjml-hero@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-hero/-/mjml-hero-4.15.3.tgz#c51d9f6d1f37acf7e35d827ce3116f8a4aaf9037" + integrity sha512-9cLAPuc69yiuzNrMZIN58j+HMK1UWPaq2i3/Fg2ZpimfcGFKRcPGCbEVh0v+Pb6/J0+kf8yIO0leH20opu3AyQ== + dependencies: + "@babel/runtime" "^7.23.9" + lodash "^4.17.21" + mjml-core "4.15.3" + +mjml-image@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-image/-/mjml-image-4.15.3.tgz#e652a4b18663c7d93cc22d88eed45f3fdb9c82ea" + integrity sha512-g1OhSdofIytE9qaOGdTPmRIp7JsCtgO0zbsn1Fk6wQh2gEL55Z40j/VoghslWAWTgT2OHFdBKnMvWtN6U5+d2Q== + dependencies: + "@babel/runtime" "^7.23.9" + lodash "^4.17.21" + mjml-core "4.15.3" + +mjml-migrate@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-migrate/-/mjml-migrate-4.15.3.tgz#65e2b335a2ffc7e29e09f96793961d0e8f081d98" + integrity sha512-sr/+35RdxZroNQVegjpfRHJ5hda9XCgaS4mK2FGO+Mb1IUevKfeEPII3F/cHDpNwFeYH3kAgyqQ22ClhGLWNBA== + dependencies: + "@babel/runtime" "^7.23.9" + js-beautify "^1.6.14" + lodash "^4.17.21" + mjml-core "4.15.3" + mjml-parser-xml "4.15.3" + yargs "^17.7.2" + +mjml-navbar@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-navbar/-/mjml-navbar-4.15.3.tgz#c9805a98f24a475dd3feece58e690838c075fdff" + integrity sha512-VsKH/Jdlf8Yu3y7GpzQV5n7JMdpqvZvTSpF6UQXL0PWOm7k6+LX+sCZimOfpHJ+wCaaybpxokjWZ71mxOoCWoA== + dependencies: + "@babel/runtime" "^7.23.9" + lodash "^4.17.21" + mjml-core "4.15.3" + +mjml-parser-xml@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-parser-xml/-/mjml-parser-xml-4.15.3.tgz#8b94550dbe0d16155ea6cd1fb34bc53dba6f59ed" + integrity sha512-Tz0UX8/JVYICLjT+U8J1f/TFxIYVYjzZHeh4/Oyta0pLpRLeZlxEd71f3u3kdnulCKMP4i37pFRDmyLXAlEuLw== + dependencies: + "@babel/runtime" "^7.23.9" + detect-node "2.1.0" + htmlparser2 "^9.1.0" + lodash "^4.17.15" + +mjml-preset-core@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-preset-core/-/mjml-preset-core-4.15.3.tgz#d4972292b7db42b51d08feb1104ad23ee5d3b87f" + integrity sha512-1zZS8P4O0KweWUqNS655+oNnVMPQ1Rq1GaZq5S9JfwT1Vh/m516lSmiTW9oko6gGHytt5s6Yj6oOeu5Zm8FoLw== + dependencies: + "@babel/runtime" "^7.23.9" + mjml-accordion "4.15.3" + mjml-body "4.15.3" + mjml-button "4.15.3" + mjml-carousel "4.15.3" + mjml-column "4.15.3" + mjml-divider "4.15.3" + mjml-group "4.15.3" + mjml-head "4.15.3" + mjml-head-attributes "4.15.3" + mjml-head-breakpoint "4.15.3" + mjml-head-font "4.15.3" + mjml-head-html-attributes "4.15.3" + mjml-head-preview "4.15.3" + mjml-head-style "4.15.3" + mjml-head-title "4.15.3" + mjml-hero "4.15.3" + mjml-image "4.15.3" + mjml-navbar "4.15.3" + mjml-raw "4.15.3" + mjml-section "4.15.3" + mjml-social "4.15.3" + mjml-spacer "4.15.3" + mjml-table "4.15.3" + mjml-text "4.15.3" + mjml-wrapper "4.15.3" + +mjml-raw@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-raw/-/mjml-raw-4.15.3.tgz#ab771a3d9b5b05583ff90653bf7ca74ec96ffc20" + integrity sha512-IGyHheOYyRchBLiAEgw3UM11kFNmBSMupu2BDdejC6ZiDhEAdG+tyERlsCwDPYtXanvFpGWULIu3XlsUPc+RZw== + dependencies: + "@babel/runtime" "^7.23.9" + lodash "^4.17.21" + mjml-core "4.15.3" + +mjml-section@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-section/-/mjml-section-4.15.3.tgz#ba2b524449b18a4fbbdf05c223a0627e02afa7a9" + integrity sha512-JfVPRXH++Hd933gmQfG8JXXCBCR6fIzC3DwiYycvanL/aW1cEQ2EnebUfQkt5QzlYjOkJEH+JpccAsq3ln6FZQ== + dependencies: + "@babel/runtime" "^7.23.9" + lodash "^4.17.21" + mjml-core "4.15.3" + +mjml-social@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-social/-/mjml-social-4.15.3.tgz#8d1ac1dfd3c56077e1106ead283a40878a2c32d9" + integrity sha512-7sD5FXrESOxpT9Z4Oh36bS6u/geuUrMP1aCg2sjyAwbPcF1aWa2k9OcatQfpRf6pJEhUZ18y6/WBBXmMVmSzXg== + dependencies: + "@babel/runtime" "^7.23.9" + lodash "^4.17.21" + mjml-core "4.15.3" + +mjml-spacer@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-spacer/-/mjml-spacer-4.15.3.tgz#9a2a4b9d51df2e9cae9fbe9848fd722ef0dfd335" + integrity sha512-3B7Qj+17EgDdAtZ3NAdMyOwLTX1jfmJuY7gjyhS2HtcZAmppW+cxqHUBwCKfvSRgTQiccmEvtNxaQK+tfyrZqA== + dependencies: + "@babel/runtime" "^7.23.9" + lodash "^4.17.21" + mjml-core "4.15.3" + +mjml-table@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-table/-/mjml-table-4.15.3.tgz#702271761e450172bd5dda9ffcb2faefed3f5db0" + integrity sha512-FLx7DcRKTdKdcOCbMyBaeudeHaHpwPveRrBm6WyQe3LXx6FfdmOh59i71/16LFQMgBOD3N4/UJkzxLzlTJzMqQ== + dependencies: + "@babel/runtime" "^7.23.9" + lodash "^4.17.21" + mjml-core "4.15.3" + +mjml-text@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-text/-/mjml-text-4.15.3.tgz#045ca711b0c18d2ba163c5a9f296a0c7ed82dbfc" + integrity sha512-+C0hxCmw9kg0XzT6vhE5mFkK6y225nC8UEQcN94K0fBCjPKkM+HqZMwGX205fzdGRi+Bxa55b/VhrIVwdv+8vw== + dependencies: + "@babel/runtime" "^7.23.9" + lodash "^4.17.21" + mjml-core "4.15.3" + +mjml-validator@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-validator/-/mjml-validator-4.15.3.tgz#c7934ca66ff41fa7293927b1328cfbafa8268ffb" + integrity sha512-Xb72KdqRwjv/qM2rJpV22syyP2N3cRQ9VVDrN6u2FSzLq02buFNxmSPJ7CKhat3PrUNdVHU75KZwOf/tz4UEhA== + dependencies: + "@babel/runtime" "^7.23.9" + +mjml-wrapper@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml-wrapper/-/mjml-wrapper-4.15.3.tgz#6526824608514561376ecfdab079275f53cc8706" + integrity sha512-ditsCijeHJrmBmObtJmQ18ddLxv5oPyMTdPU8Di8APOnD2zPk7Z4UAuJSl7HXB45oFiivr3MJf4koFzMUSZ6Gg== + dependencies: + "@babel/runtime" "^7.23.9" + lodash "^4.17.21" + mjml-core "4.15.3" + mjml-section "4.15.3" + +mjml@4.15.3: + version "4.15.3" + resolved "https://registry.yarnpkg.com/mjml/-/mjml-4.15.3.tgz#d46996d63e957ae946b2da6ca78fcef5186beee9" + integrity sha512-bW2WpJxm6HS+S3Yu6tq1DUPFoTxU9sPviUSmnL7Ua+oVO3WA5ILFWqvujUlz+oeuM+HCwEyMiP5xvKNPENVjYA== + dependencies: + "@babel/runtime" "^7.23.9" + mjml-cli "4.15.3" + mjml-core "4.15.3" + mjml-migrate "4.15.3" + mjml-preset-core "4.15.3" + mjml-validator "4.15.3" + +no-case@^2.2.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" + integrity sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ== + dependencies: + lower-case "^1.1.1" + +node-fetch@^2.6.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + +nopt@^7.2.0: + version "7.2.1" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-7.2.1.tgz#1cac0eab9b8e97c9093338446eddd40b2c8ca1e7" + integrity sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w== + dependencies: + abbrev "^2.0.0" + +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== + +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" + +package-json-from-dist@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" + integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== + +param-case@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247" + integrity sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w== + dependencies: + no-case "^2.2.0" + +parse5-htmlparser2-tree-adapter@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz#b5a806548ed893a43e24ccb42fbb78069311e81b" + integrity sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g== + dependencies: + domhandler "^5.0.3" + parse5 "^7.0.0" + +parse5@^7.0.0: + version "7.2.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.2.1.tgz#8928f55915e6125f430cc44309765bf17556a33a" + integrity sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ== + dependencies: + entities "^4.5.0" + +parseley@^0.12.0: + version "0.12.1" + resolved "https://registry.yarnpkg.com/parseley/-/parseley-0.12.1.tgz#4afd561d50215ebe259e3e7a853e62f600683aef" + integrity sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw== + dependencies: + leac "^0.6.0" + peberminta "^0.9.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-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + +peberminta@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/peberminta/-/peberminta-0.8.0.tgz#acf7b105f3d13c8ac28cad81f2f5fe4698507590" + integrity sha512-YYEs+eauIjDH5nUEGi18EohWE0nV2QbGTqmxQcqgZ/0g+laPCQmuIqq7EBLVi9uim9zMgfJv0QBZEnQ3uHw/Tw== + +peberminta@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/peberminta/-/peberminta-0.9.0.tgz#8ec9bc0eb84b7d368126e71ce9033501dca2a352" + integrity sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ== + +picomatch@^2.0.4, picomatch@^2.2.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +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== + +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" + +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== + +relateurl@^0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" + integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +selderee@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/selderee/-/selderee-0.11.0.tgz#6af0c7983e073ad3e35787ffe20cefd9daf0ec8a" + integrity sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA== + dependencies: + parseley "^0.12.0" + +semver@^7.5.3: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + +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== + +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + +slick@^1.12.2: + version "1.12.2" + resolved "https://registry.yarnpkg.com/slick/-/slick-1.12.2.tgz#bd048ddb74de7d1ca6915faa4a57570b3550c2d7" + integrity sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A== + +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== + +"string-width-cjs@npm: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@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-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" + +"strip-ansi-cjs@npm: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@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-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" + +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" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +uglify-js@^3.5.1: + version "3.19.3" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.19.3.tgz#82315e9bbc6f2b25888858acd1fff8441035b77f" + integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== + +upper-case@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598" + integrity sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA== + +valid-data-url@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/valid-data-url/-/valid-data-url-3.0.1.tgz#826c1744e71b5632e847dd15dbd45b9fb38aa34f" + integrity sha512-jOWVmzVceKlVVdwjNSenT4PbGghU0SBIizAev8ofZVgivk/TVHXSbNL8LP6M3spZvkR9/QolkyJavGSX5Cs0UA== + +web-resource-inliner@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/web-resource-inliner/-/web-resource-inliner-6.0.1.tgz#df0822f0a12028805fe80719ed52ab6526886e02" + integrity sha512-kfqDxt5dTB1JhqsCUQVFDj0rmY+4HLwGQIsLPbyrsN9y9WV/1oFDSx3BQ4GfCv9X+jVeQ7rouTqwK53rA/7t8A== + dependencies: + ansi-colors "^4.1.1" + escape-goat "^3.0.0" + htmlparser2 "^5.0.0" + mime "^2.4.6" + node-fetch "^2.6.0" + valid-data-url "^3.0.0" + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.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" + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +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" + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1"